]> Dogcows Code - chaz/tar/blobdiff - src/tar.c
tar: live within system-supplied limits on file descriptors
[chaz/tar] / src / tar.c
index b0e039dbc7ff28d6970103fd4d5dabd9801d09db..6fd117c636702f564511be0d3c7c4876b849efdb 100644 (file)
--- a/src/tar.c
+++ b/src/tar.c
@@ -1,13 +1,13 @@
 /* A tar (tape archiver) program.
 
    Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000,
-   2001, 2003, 2004, 2005 Free Software Foundation, Inc.
+   2001, 2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
 
    Written by John Gilmore, starting 1985-08-25.
 
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
-   Free Software Foundation; either version 2, or (at your option) any later
+   Free Software Foundation; either version 3, or (at your option) any later
    version.
 
    This program is distributed in the hope that it will be useful, but
@@ -23,6 +23,9 @@
 
 #include <fnmatch.h>
 #include <argp.h>
+#include <argp-namefrob.h>
+#include <argp-fmtstream.h>
+#include <argp-version-etc.h>
 
 #include <signal.h>
 #if ! defined SIGCHLD && defined SIGCLD
 #define GLOBAL
 #include "common.h"
 
+#include <argmatch.h>
+#include <closeout.h>
+#include <configmake.h>
+#include <exitfail.h>
 #include <getdate.h>
-#include <localedir.h>
 #include <rmt.h>
+#include <rmt-command.h>
 #include <prepargs.h>
 #include <quotearg.h>
+#include <version-etc.h>
 #include <xstrtol.h>
+#include <stdopen.h>
+#include <priv-set.h>
 
 /* Local declarations.  */
 
@@ -64,7 +74,7 @@
 static const char *stdin_used_by;
 
 /* Doesn't return if stdin already requested.  */
-void
+static void
 request_stdin (const char *option)
 {
   if (stdin_used_by)
@@ -74,12 +84,15 @@ request_stdin (const char *option)
   stdin_used_by = option;
 }
 
-/* Returns true if and only if the user typed 'y' or 'Y'.  */
+extern int rpmatch (char const *response);
+
+/* Returns true if and only if the user typed an affirmative response.  */
 int
 confirm (const char *message_action, const char *message_name)
 {
   static FILE *confirm_file;
   static int confirm_file_EOF;
+  bool status = false;
 
   if (!confirm_file)
     {
@@ -99,22 +112,24 @@ confirm (const char *message_action, const char *message_name)
   fprintf (stdlis, "%s %s?", message_action, quote (message_name));
   fflush (stdlis);
 
-  {
-    int reply = confirm_file_EOF ? EOF : getc (confirm_file);
-    int character;
+  if (!confirm_file_EOF)
+    {
+      char *response = NULL;
+      size_t response_size = 0;
+      if (getline (&response, &response_size, confirm_file) < 0)
+       confirm_file_EOF = 1;
+      else
+       status = rpmatch (response) > 0;
+      free (response);
+    }
 
-    for (character = reply;
-        character != '\n';
-        character = getc (confirm_file))
-      if (character == EOF)
-       {
-         confirm_file_EOF = 1;
-         fputc ('\n', stdlis);
-         fflush (stdlis);
-         break;
-       }
-    return reply == 'y' || reply == 'Y';
-  }
+  if (confirm_file_EOF)
+    {
+      fputc ('\n', stdlis);
+      fflush (stdlis);
+    }
+
+  return status;
 }
 
 static struct fmttab {
@@ -130,7 +145,7 @@ static struct fmttab {
 #endif
   { "gnu",     GNU_FORMAT },
   { "pax",     POSIX_FORMAT }, /* An alias for posix */
-  { NULL,       0 }
+  { NULL,      0 }
 };
 
 static void
@@ -146,7 +161,7 @@ set_archive_format (char const *name)
   archive_format = p->fmt;
 }
 
-static const char *
+const char *
 archive_format_string (enum archive_format fmt)
 {
   struct fmttab const *p;
@@ -162,95 +177,184 @@ archive_format_string (enum archive_format fmt)
 static void
 assert_format(unsigned fmt_mask)
 {
-  if ((FORMAT_MASK(archive_format) & fmt_mask) == 0)
+  if ((FORMAT_MASK (archive_format) & fmt_mask) == 0)
     USAGE_ERROR ((0, 0,
                  _("GNU features wanted on incompatible archive format")));
 }
 
+const char *
+subcommand_string (enum subcommand c)
+{
+  switch (c)
+    {
+    case UNKNOWN_SUBCOMMAND:
+      return "unknown?";
+
+    case APPEND_SUBCOMMAND:
+      return "-r";
+
+    case CAT_SUBCOMMAND:
+      return "-A";
+
+    case CREATE_SUBCOMMAND:
+      return "-c";
+
+    case DELETE_SUBCOMMAND:
+      return "-D";
+
+    case DIFF_SUBCOMMAND:
+      return "-d";
+
+    case EXTRACT_SUBCOMMAND:
+      return "-x";
+
+    case LIST_SUBCOMMAND:
+      return "-t";
+
+    case UPDATE_SUBCOMMAND:
+      return "-u";
+
+    case TEST_LABEL_SUBCOMMAND:
+      return "--test-label";
+    }
+  abort ();
+}
+
+static void
+tar_list_quoting_styles (struct obstack *stk, char const *prefix)
+{
+  int i;
+  size_t prefixlen = strlen (prefix);
+
+  for (i = 0; quoting_style_args[i]; i++)
+    {
+      obstack_grow (stk, prefix, prefixlen);
+      obstack_grow (stk, quoting_style_args[i],
+                   strlen (quoting_style_args[i]));
+      obstack_1grow (stk, '\n');
+    }
+}
+
+static void
+tar_set_quoting_style (char *arg)
+{
+  int i;
+
+  for (i = 0; quoting_style_args[i]; i++)
+    if (strcmp (arg, quoting_style_args[i]) == 0)
+      {
+       set_quoting_style (NULL, i);
+       return;
+      }
+  FATAL_ERROR ((0, 0,
+               _("Unknown quoting style `%s'. Try `%s --quoting-style=help' to get a list."), arg, program_invocation_short_name));
+}
 
 \f
 /* Options.  */
 
-/* For long options that unconditionally set a single flag, we have getopt
-   do it.  For the others, we share the code for the equivalent short
-   named option, the name of which is stored in the otherwise-unused `val'
-   field of the `struct option'; for long options that have no equivalent
-   short option, we use non-characters as pseudo short options,
-   starting at CHAR_MAX + 1 and going upwards.  */
-
 enum
 {
   ANCHORED_OPTION = CHAR_MAX + 1,
   ATIME_PRESERVE_OPTION,
   BACKUP_OPTION,
+  CHECK_DEVICE_OPTION,
   CHECKPOINT_OPTION,
-  CHECK_LINKS_OPTION,
+  CHECKPOINT_ACTION_OPTION,
+  DELAY_DIRECTORY_RESTORE_OPTION,
+  HARD_DEREFERENCE_OPTION,
   DELETE_OPTION,
-  EXCLUDE_OPTION,
+  EXCLUDE_BACKUPS_OPTION,
   EXCLUDE_CACHES_OPTION,
+  EXCLUDE_CACHES_UNDER_OPTION,
+  EXCLUDE_CACHES_ALL_OPTION,
+  EXCLUDE_OPTION,
+  EXCLUDE_TAG_OPTION,
+  EXCLUDE_TAG_UNDER_OPTION,
+  EXCLUDE_TAG_ALL_OPTION,
+  EXCLUDE_VCS_OPTION,
   FORCE_LOCAL_OPTION,
+  FULL_TIME_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,
+  LEVEL_OPTION,
+  LZIP_OPTION,
+  LZMA_OPTION,
+  LZOP_OPTION,
   MODE_OPTION,
+  MTIME_OPTION,
   NEWER_MTIME_OPTION,
   NO_ANCHORED_OPTION,
+  NO_AUTO_COMPRESS_OPTION,
+  NO_CHECK_DEVICE_OPTION,
+  NO_DELAY_DIRECTORY_RESTORE_OPTION,
   NO_IGNORE_CASE_OPTION,
   NO_IGNORE_COMMAND_ERROR_OPTION,
+  NO_NULL_OPTION,
   NO_OVERWRITE_DIR_OPTION,
+  NO_QUOTE_CHARS_OPTION,
   NO_RECURSION_OPTION,
   NO_SAME_OWNER_OPTION,
   NO_SAME_PERMISSIONS_OPTION,
+  NO_SEEK_OPTION,
   NO_UNQUOTE_OPTION,
-  NO_WILDCARDS_OPTION,
   NO_WILDCARDS_MATCH_SLASH_OPTION,
+  NO_WILDCARDS_OPTION,
   NULL_OPTION,
   NUMERIC_OWNER_OPTION,
   OCCURRENCE_OPTION,
   OLD_ARCHIVE_OPTION,
   ONE_FILE_SYSTEM_OPTION,
+  OVERWRITE_DIR_OPTION,
   OVERWRITE_OPTION,
   OWNER_OPTION,
   PAX_OPTION,
   POSIX_OPTION,
   PRESERVE_OPTION,
+  QUOTE_CHARS_OPTION,
+  QUOTING_STYLE_OPTION,
   RECORD_SIZE_OPTION,
   RECURSION_OPTION,
   RECURSIVE_UNLINK_OPTION,
   REMOVE_FILES_OPTION,
+  RESTRICT_OPTION,
   RMT_COMMAND_OPTION,
   RSH_COMMAND_OPTION,
   SAME_OWNER_OPTION,
   SHOW_DEFAULTS_OPTION,
   SHOW_OMITTED_DIRS_OPTION,
+  SHOW_TRANSFORMED_NAMES_OPTION,
+  SPARSE_VERSION_OPTION,
   STRIP_COMPONENTS_OPTION,
   SUFFIX_OPTION,
-  TO_COMMAND_OPTION,
+  TEST_LABEL_OPTION,
   TOTALS_OPTION,
+  TO_COMMAND_OPTION,
+  TRANSFORM_OPTION,
   UNQUOTE_OPTION,
-  USAGE_OPTION,
-  USE_COMPRESS_PROGRAM_OPTION,
   UTC_OPTION,
-  VERSION_OPTION,
   VOLNO_FILE_OPTION,
-  WILDCARDS_OPTION,
-  WILDCARDS_MATCH_SLASH_OPTION
+  WARNING_OPTION,
+  WILDCARDS_MATCH_SLASH_OPTION,
+  WILDCARDS_OPTION
 };
 
 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\
+static char const 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\
   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\
+  tar -xf archive.tar          # Extract all files from archive.tar.\n")
+"\v"
+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\
   none, off       never make backups\n\
   t, numbered     make numbered backups\n\
@@ -260,348 +364,570 @@ The version control may be set with --backup or VERSION_CONTROL, values are:\n\n
 
 /* NOTE:
 
-   Available option letters are DEIJQY and aeqy. Consider the following
+   Available option letters are DEQY and eqy. Consider the following
    assignments:
 
-   [For Solaris tar compatibility]
+   [For Solaris tar compatibility =/= Is it important at all?]
    e  exit immediately with a nonzero exit status if unexpected errors occur
    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 */
+   Y  per-block gzip compression.
+
+   Additionally, the 'n' letter is assigned for option --seek, which
+   is probably not needed and should be marked as deprecated, so that
+   -n may become available in the future.
+*/
 
 static struct argp_option options[] = {
+#define GRID 10
   {NULL, 0, NULL, 0,
-   N_("Main operation mode:"), 0},
+   N_("Main operation mode:"), GRID },
 
   {"list", 't', 0, 0,
-   N_("list the contents of an archive"), 10 },
+   N_("list the contents of an archive"), GRID+1 },
   {"extract", 'x', 0, 0,
-   N_("extract files from an archive"), 10 },
-  {"get", 0, 0, OPTION_ALIAS, NULL, 0 },
+   N_("extract files from an archive"), GRID+1 },
+  {"get", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
   {"create", 'c', 0, 0,
-   N_("create a new archive"), 10 },
+   N_("create a new archive"), GRID+1 },
   {"diff", 'd', 0, 0,
-   N_("find differences between archive and file system"), 10 },
-  {"compare", 0, 0, OPTION_ALIAS, NULL, 10},
+   N_("find differences between archive and file system"), GRID+1 },
+  {"compare", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
   {"append", 'r', 0, 0,
-   N_("append files to the end of an archive"), 10 },
+   N_("append files to the end of an archive"), GRID+1 },
   {"update", 'u', 0, 0,
-   N_("only append files newer than copy in archive"), 10 },
+   N_("only append files newer than copy in archive"), GRID+1 },
   {"catenate", 'A', 0, 0,
-   N_("append tar files to an archive"), 10 },
-  {"concatenate", 0, 0, OPTION_ALIAS, NULL, 10},
+   N_("append tar files to an archive"), GRID+1 },
+  {"concatenate", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
   {"delete", DELETE_OPTION, 0, 0,
-   N_("delete from the archive (not on mag tapes!)"), 10 },
+   N_("delete from the archive (not on mag tapes!)"), GRID+1 },
+  {"test-label", TEST_LABEL_OPTION, NULL, 0,
+   N_("test the archive volume label and exit"), GRID+1 },
+#undef GRID
 
+#define GRID 20
   {NULL, 0, NULL, 0,
-   N_("Operation modifiers:"), 20},
+   N_("Operation modifiers:"), GRID },
 
   {"sparse", 'S', 0, 0,
-   N_("handle sparse files efficiently"), 21 },
+   N_("handle sparse files efficiently"), GRID+1 },
+  {"sparse-version", SPARSE_VERSION_OPTION, N_("MAJOR[.MINOR]"), 0,
+   N_("set version of the sparse format to use (implies --sparse)"), GRID+1},
   {"incremental", 'G', 0, 0,
-   N_("handle old GNU-format incremental backup"), 21 },
+   N_("handle old GNU-format incremental backup"), GRID+1 },
   {"listed-incremental", 'g', N_("FILE"), 0,
-   N_("handle new GNU-format incremental backup"), 21 },
+   N_("handle new GNU-format incremental backup"), GRID+1 },
+  {"level", LEVEL_OPTION, N_("NUMBER"), 0,
+   N_("dump level for created listed-incremental archive"), GRID+1 },
   {"ignore-failed-read", IGNORE_FAILED_READ_OPTION, 0, 0,
-   N_("do not exit with nonzero on unreadable files"), 21 },
+   N_("do not exit with nonzero on unreadable files"), GRID+1 },
   {"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 },
+   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 the -T option;"
+      " NUMBER defaults to 1"), GRID+1 },
   {"seek", 'n', NULL, 0,
-   N_("archive is seekable"), 21 },
-
+   N_("archive is seekable"), GRID+1 },
+  {"no-seek", NO_SEEK_OPTION, NULL, 0,
+   N_("archive is not seekable"), GRID+1 },
+  {"no-check-device", NO_CHECK_DEVICE_OPTION, NULL, 0,
+   N_("do not check device numbers when creating incremental archives"),
+   GRID+1 },
+  {"check-device", CHECK_DEVICE_OPTION, NULL, 0,
+   N_("check device numbers when creating incremental archives (default)"),
+   GRID+1 },
+#undef GRID
+
+#define GRID 30
   {NULL, 0, NULL, 0,
-   N_("Overwrite control:"), 30},
+   N_("Overwrite control:"), GRID },
 
   {"verify", 'W', 0, 0,
-   N_("attempt to verify the archive after writing it"), 31 },
+   N_("attempt to verify the archive after writing it"), GRID+1 },
   {"remove-files", REMOVE_FILES_OPTION, 0, 0,
-   N_("remove files after adding them to the archive"), 31 },
+   N_("remove files after adding them to the archive"), GRID+1 },
   {"keep-old-files", 'k', 0, 0,
-   N_("don't replace existing files when extracting"), 31 },
+   N_("don't replace existing files when extracting"), GRID+1 },
   {"keep-newer-files", KEEP_NEWER_FILES_OPTION, 0, 0,
-   N_("don't replace existing files that are newer than their archive copies"), 31 },
+   N_("don't replace existing files that are newer than their archive copies"), GRID+1 },
   {"overwrite", OVERWRITE_OPTION, 0, 0,
-   N_("overwrite existing files when extracting"), 31 },
+   N_("overwrite existing files when extracting"), GRID+1 },
   {"unlink-first", 'U', 0, 0,
-   N_("remove each file prior to extracting over it"), 31 },
+   N_("remove each file prior to extracting over it"), GRID+1 },
   {"recursive-unlink", RECURSIVE_UNLINK_OPTION, 0, 0,
-   N_("empty hierarchies prior to extracting directory"), 31 },
+   N_("empty hierarchies prior to extracting directory"), GRID+1 },
   {"no-overwrite-dir", NO_OVERWRITE_DIR_OPTION, 0, 0,
-   N_("preserve metadata of existing directories"), 31 },
+   N_("preserve metadata of existing directories"), GRID+1 },
+  {"overwrite-dir", OVERWRITE_DIR_OPTION, 0, 0,
+   N_("overwrite metadata of existing directories when extracting (default)"),
+   GRID+1 },
+#undef GRID
 
+#define GRID 40
   {NULL, 0, NULL, 0,
-   N_("Select output stream:"), 40},
+   N_("Select output stream:"), GRID },
 
   {"to-stdout", 'O', 0, 0,
-   N_("extract files to standard output"), 41 },
+   N_("extract files to standard output"), GRID+1 },
   {"to-command", TO_COMMAND_OPTION, N_("COMMAND"), 0,
-   N_("pipe extracted files to another program"), 41 },
+   N_("pipe extracted files to another program"), GRID+1 },
   {"ignore-command-error", IGNORE_COMMAND_ERROR_OPTION, 0, 0,
-   N_("ignore exit codes of children"), 41 },
+   N_("ignore exit codes of children"), GRID+1 },
   {"no-ignore-command-error", NO_IGNORE_COMMAND_ERROR_OPTION, 0, 0,
-   N_("treat non-zero exit codes of children as error"), 41 },
+   N_("treat non-zero exit codes of children as error"), GRID+1 },
+#undef GRID
 
+#define GRID 50
   {NULL, 0, NULL, 0,
-   N_("Handling of file attributes:"), 50 },
+   N_("Handling of file attributes:"), GRID },
 
   {"owner", OWNER_OPTION, N_("NAME"), 0,
-   N_("force NAME as owner for added files"), 51 },
+   N_("force NAME as owner for added files"), GRID+1 },
   {"group", GROUP_OPTION, N_("NAME"), 0,
-   N_("force NAME as group for added files"), 51 },
+   N_("force NAME as group for added files"), GRID+1 },
+  {"mtime", MTIME_OPTION, N_("DATE-OR-FILE"), 0,
+   N_("set mtime for added files from DATE-OR-FILE"), GRID+1 },
   {"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 },
+   N_("force (symbolic) mode CHANGES for added files"), GRID+1 },
+  {"atime-preserve", ATIME_PRESERVE_OPTION,
+   N_("METHOD"), OPTION_ARG_OPTIONAL,
+   N_("preserve access times on dumped files, either by restoring the times"
+      " after reading (METHOD='replace'; default) or by not setting the times"
+      " in the first place (METHOD='system')"), GRID+1 },
   {"touch", 'm', 0, 0,
-   N_("don't extract file modified time"), 51 },
+   N_("don't extract file modified time"), GRID+1 },
   {"same-owner", SAME_OWNER_OPTION, 0, 0,
-   N_("try extracting files with the same ownership"), 51 },
+   N_("try extracting files with the same ownership as exists in the archive (default for superuser)"), GRID+1 },
   {"no-same-owner", NO_SAME_OWNER_OPTION, 0, 0,
-   N_("extract files as yourself"), 51 },
+   N_("extract files as yourself (default for ordinary users)"), GRID+1 },
   {"numeric-owner", NUMERIC_OWNER_OPTION, 0, 0,
-   N_("always use numbers for user/group names"), 51 },
+   N_("always use numbers for user/group names"), GRID+1 },
   {"preserve-permissions", 'p', 0, 0,
    N_("extract information about file permissions (default for superuser)"),
-   51 },
-  {"same-permissions", 0, 0, OPTION_ALIAS, NULL, 51 },
+   GRID+1 },
+  {"same-permissions", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
   {"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 },
+   N_("apply the user's umask when extracting permissions from the archive (default for ordinary users)"), GRID+1 },
   {"preserve-order", 's', 0, 0,
-   N_("sort names to extract to match archive"), 51 },
-  {"same-order", 0, 0, OPTION_ALIAS, NULL, 51 },
+   N_("sort names to extract to match archive"), GRID+1 },
+  {"same-order", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
   {"preserve", PRESERVE_OPTION, 0, 0,
-   N_("same as both -p and -s"), 51 },
-
+   N_("same as both -p and -s"), GRID+1 },
+  {"delay-directory-restore", DELAY_DIRECTORY_RESTORE_OPTION, 0, 0,
+   N_("delay setting modification times and permissions of extracted"
+      " directories until the end of extraction"), GRID+1 },
+  {"no-delay-directory-restore", NO_DELAY_DIRECTORY_RESTORE_OPTION, 0, 0,
+   N_("cancel the effect of --delay-directory-restore option"), GRID+1 },
+#undef GRID
+
+#define GRID 60
   {NULL, 0, NULL, 0,
-   N_("Device selection and switching:"), 60 },
+   N_("Device selection and switching:"), GRID },
 
   {"file", 'f', N_("ARCHIVE"), 0,
-   N_("use archive file or device ARCHIVE"), 61 },
+   N_("use archive file or device ARCHIVE"), GRID+1 },
   {"force-local", FORCE_LOCAL_OPTION, 0, 0,
-   N_("archive file is local even if it has a colon"), 61 },
+   N_("archive file is local even if it has a colon"), GRID+1 },
   {"rmt-command", RMT_COMMAND_OPTION, N_("COMMAND"), 0,
-   N_("use given rmt COMMAND instead of rmt"), 61 },
+   N_("use given rmt COMMAND instead of rmt"), GRID+1 },
   {"rsh-command", RSH_COMMAND_OPTION, N_("COMMAND"), 0,
-   N_("use remote COMMAND instead of rsh"), 61 },
+   N_("use remote COMMAND instead of rsh"), GRID+1 },
 #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 },
+   N_("specify drive and density"), GRID+1 },
 #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 },
+  {NULL, '0', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '1', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '2', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '3', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '4', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '5', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '6', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '7', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '8', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+  {NULL, '9', NULL, OPTION_HIDDEN, NULL, GRID+1 },
 
   {"multi-volume", 'M', 0, 0,
-   N_("create/list/extract multi-volume archive"), 61 },
+   N_("create/list/extract multi-volume archive"), GRID+1 },
   {"tape-length", 'L', N_("NUMBER"), 0,
-   N_("change tape after writing NUMBER x 1024 bytes"), 61 },
+   N_("change tape after writing NUMBER x 1024 bytes"), GRID+1 },
   {"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 },
+   N_("run script at end of each tape (implies -M)"), GRID+1 },
+  {"new-volume-script", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
   {"volno-file", VOLNO_FILE_OPTION, N_("FILE"), 0,
-   N_("use/update the volume number in FILE"), 61 },
+   N_("use/update the volume number in FILE"), GRID+1 },
+#undef GRID
 
+#define GRID 70
   {NULL, 0, NULL, 0,
-   N_("Device blocking:"), 70 },
+   N_("Device blocking:"), GRID },
 
   {"blocking-factor", 'b', N_("BLOCKS"), 0,
-   N_("BLOCKS x 512 bytes per record"), 71 },
+   N_("BLOCKS x 512 bytes per record"), GRID+1 },
   {"record-size", RECORD_SIZE_OPTION, N_("NUMBER"), 0,
-   N_("NUMBER of bytes per record, multiple of 512"), 71 },
+   N_("NUMBER of bytes per record, multiple of 512"), GRID+1 },
   {"ignore-zeros", 'i', 0, 0,
-   N_("ignore zeroed blocks in archive (means EOF)"), 71 },
+   N_("ignore zeroed blocks in archive (means EOF)"), GRID+1 },
   {"read-full-records", 'B', 0, 0,
-   N_("reblock as we read (for 4.2BSD pipes)"), 71 },
+   N_("reblock as we read (for 4.2BSD pipes)"), GRID+1 },
+#undef GRID
 
+#define GRID 80
   {NULL, 0, NULL, 0,
-   N_("Archive format selection:"), 80 },
+   N_("Archive format selection:"), GRID },
 
   {"format", 'H', N_("FORMAT"), 0,
-   N_("create archive of the given format."), 81 },
+   N_("create archive of the given format"), GRID+1 },
 
-  {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},
+  {NULL, 0, NULL, 0, N_("FORMAT is one of the following:"), GRID+2 },
+  {"  v7", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("old V7 tar format"),
+   GRID+3 },
   {"  oldgnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
-   N_("GNU format as per tar <= 1.12"), 83},
+   N_("GNU format as per tar <= 1.12"), GRID+3 },
   {"  gnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
-   N_("GNU tar 1.13.x format"), 83},
+   N_("GNU tar 1.13.x format"), GRID+3 },
   {"  ustar", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
-   N_("POSIX 1003.1-1988 (ustar) format"), 83 },
+   N_("POSIX 1003.1-1988 (ustar) format"), GRID+3 },
   {"  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 },
+   N_("POSIX 1003.1-2001 (pax) format"), GRID+3 },
+  {"  posix", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("same as pax"), GRID+3 },
 
   {"old-archive", OLD_ARCHIVE_OPTION, 0, 0, /* FIXME */
-   N_("same as --format=v7"), 88 },
-  {"portability", 0, 0, OPTION_ALIAS, NULL, 88 },
+   N_("same as --format=v7"), GRID+8 },
+  {"portability", 0, 0, OPTION_ALIAS, NULL, GRID+8 },
   {"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 },
+   N_("same as --format=posix"), GRID+8 },
+  {"pax-option", PAX_OPTION, N_("keyword[[:]=value][,keyword[[:]=value]]..."), 0,
+   N_("control pax keywords"), GRID+8 },
   {"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 },
+   N_("create archive with volume name TEXT; at list/extract time, use TEXT as a globbing pattern for volume name"), GRID+8 },
+#undef GRID
 
+#define GRID 90
+  {NULL, 0, NULL, 0,
+   N_("Compression options:"), GRID },
+  {"auto-compress", 'a', 0, 0,
+   N_("use archive suffix to determine the compression program"), GRID+1 },
+  {"no-auto-compress", NO_AUTO_COMPRESS_OPTION, 0, 0,
+   N_("do not use archive suffix to determine the compression program"),
+   GRID+1 },
+  {"use-compress-program", 'I', N_("PROG"), 0,
+   N_("filter through PROG (must accept -d)"), GRID+1 },
+  /* Note: docstrings for the options below are generated by tar_help_filter */
+  {"bzip2", 'j', 0, 0, NULL, GRID+1 },
+  {"gzip", 'z', 0, 0, NULL, GRID+1 },
+  {"gunzip", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+  {"ungzip", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+  {"compress", 'Z', 0, 0, NULL, GRID+1 },
+  {"uncompress", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+  {"lzip", LZIP_OPTION, 0, 0, NULL, GRID+1 },
+  {"lzma", LZMA_OPTION, 0, 0, NULL, GRID+1 },
+  {"lzop", LZOP_OPTION, 0, 0, NULL, GRID+1 },
+  {"xz", 'J', 0, 0, NULL, GRID+1 },
+#undef GRID
+
+#define GRID 100
   {NULL, 0, NULL, 0,
-   N_("Local file selection:"), 90 },
+   N_("Local file selection:"), GRID },
 
   {"add-file", ARGP_KEY_ARG, N_("FILE"), 0,
-   N_("add given FILE to the archive (useful if its name starts with a dash)"), 91},
+   N_("add given FILE to the archive (useful if its name starts with a dash)"), GRID+1 },
   {"directory", 'C', N_("DIR"), 0,
-   N_("change to directory DIR"), 91 },
+   N_("change to directory DIR"), GRID+1 },
   {"files-from", 'T', N_("FILE"), 0,
-   N_("get names to extract or create from FILE"), 91 },
+   N_("get names to extract or create from FILE"), GRID+1 },
   {"null", NULL_OPTION, 0, 0,
-   N_("-T reads null-terminated names, disable -C"), 91 },
+   N_("-T reads null-terminated names, disable -C"), GRID+1 },
+  {"no-null", NO_NULL_OPTION, 0, 0,
+   N_("disable the effect of the previous --null option"), GRID+1 },
   {"unquote", UNQUOTE_OPTION, 0, 0,
-   N_("unquote filenames read with -T (default)"), 91 },
+   N_("unquote filenames read with -T (default)"), GRID+1 },
   {"no-unquote", NO_UNQUOTE_OPTION, 0, 0,
-   N_("do not unquote filenames read with -T"), 91 },
+   N_("do not unquote filenames read with -T"), GRID+1 },
   {"exclude", EXCLUDE_OPTION, N_("PATTERN"), 0,
-   N_("exclude files, given as a PATTERN"), 91 },
+   N_("exclude files, given as a PATTERN"), GRID+1 },
   {"exclude-from", 'X', N_("FILE"), 0,
-   N_("exclude patterns listed in FILE"), 91 },
+   N_("exclude patterns listed in FILE"), GRID+1 },
   {"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 },
+   N_("exclude contents of directories containing CACHEDIR.TAG, "
+      "except for the tag file itself"), GRID+1 },
+  {"exclude-caches-under", EXCLUDE_CACHES_UNDER_OPTION, 0, 0,
+   N_("exclude everything under directories containing CACHEDIR.TAG"),
+   GRID+1 },
+  {"exclude-caches-all", EXCLUDE_CACHES_ALL_OPTION, 0, 0,
+   N_("exclude directories containing CACHEDIR.TAG"), GRID+1 },
+  {"exclude-tag", EXCLUDE_TAG_OPTION, N_("FILE"), 0,
+   N_("exclude contents of directories containing FILE, except"
+      " for FILE itself"), GRID+1 },
+  {"exclude-tag-under", EXCLUDE_TAG_UNDER_OPTION, N_("FILE"), 0,
+   N_("exclude everything under directories containing FILE"), GRID+1 },
+  {"exclude-tag-all", EXCLUDE_TAG_ALL_OPTION, N_("FILE"), 0,
+   N_("exclude directories containing FILE"), GRID+1 },
+  {"exclude-vcs", EXCLUDE_VCS_OPTION, NULL, 0,
+   N_("exclude version control system directories"), GRID+1 },
+  {"exclude-backups", EXCLUDE_BACKUPS_OPTION, NULL, 0,
+   N_("exclude backup and lock files"), GRID+1 },
   {"no-recursion", NO_RECURSION_OPTION, 0, 0,
-   N_("avoid descending automatically in directories"), 91 },
+   N_("avoid descending automatically in directories"), GRID+1 },
   {"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},
+   N_("stay in local file system when creating archive"), GRID+1 },
   {"recursion", RECURSION_OPTION, 0, 0,
-   N_("recurse into directories (default)"), 91 },
+   N_("recurse into directories (default)"), GRID+1 },
   {"absolute-names", 'P', 0, 0,
-   N_("don't strip leading `/'s from file names"), 91 },
+   N_("don't strip leading `/'s from file names"), GRID+1 },
   {"dereference", 'h', 0, 0,
-   N_("follow symlinks; archive and dump the files they point to"), 91 },
+   N_("follow symlinks; archive and dump the files they point to"), GRID+1 },
+  {"hard-dereference", HARD_DEREFERENCE_OPTION, 0, 0,
+   N_("follow hard links; archive and dump the files they refer to"), GRID+1 },
   {"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 },
+   N_("begin at member MEMBER-NAME in the archive"), GRID+1 },
   {"newer", 'N', N_("DATE-OR-FILE"), 0,
-   N_("only store files newer than DATE-OR-FILE"), 91 },
+   N_("only store files newer than DATE-OR-FILE"), GRID+1 },
+  {"after-date", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
   {"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 },
+   N_("compare date and time when data changed only"), GRID+1 },
   {"backup", BACKUP_OPTION, N_("CONTROL"), OPTION_ARG_OPTIONAL,
-   N_("backup before removal, choose version CONTROL"), 91 },
+   N_("backup before removal, choose version CONTROL"), GRID+1 },
   {"suffix", SUFFIX_OPTION, N_("STRING"), 0,
-   N_("backup before removal, override usual suffix ('~' unless overridden by environment variable SIMPLE_BACKUP_SUFFIX)"), 91 },
+   N_("backup before removal, override usual suffix ('~' unless overridden by environment variable SIMPLE_BACKUP_SUFFIX)"), GRID+1 },
+#undef GRID
+
+#define GRID 110
+  {NULL, 0, NULL, 0,
+   N_("File name transformations:"), GRID },
+  {"strip-components", STRIP_COMPONENTS_OPTION, N_("NUMBER"), 0,
+   N_("strip NUMBER leading components from file names on extraction"),
+   GRID+1 },
+  {"transform", TRANSFORM_OPTION, N_("EXPRESSION"), 0,
+   N_("use sed replace EXPRESSION to transform file names"), GRID+1 },
+  {"xform", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+#undef GRID
+
+#define GRID 120
+  {NULL, 0, NULL, 0,
+   N_("File name matching options (affect both exclude and include patterns):"),
+   GRID },
+  {"ignore-case", IGNORE_CASE_OPTION, 0, 0,
+   N_("ignore case"), GRID+1 },
+  {"anchored", ANCHORED_OPTION, 0, 0,
+   N_("patterns match file name start"), GRID+1 },
+  {"no-anchored", NO_ANCHORED_OPTION, 0, 0,
+   N_("patterns match after any `/' (default for exclusion)"), GRID+1 },
+  {"no-ignore-case", NO_IGNORE_CASE_OPTION, 0, 0,
+   N_("case sensitive matching (default)"), GRID+1 },
   {"wildcards", WILDCARDS_OPTION, 0, 0,
-   N_("exclude patterns use wildcards (default)"), 91 },
+   N_("use wildcards (default for exclusion)"), GRID+1 },
+  {"no-wildcards", NO_WILDCARDS_OPTION, 0, 0,
+   N_("verbatim string matching"), GRID+1 },
+  {"no-wildcards-match-slash", NO_WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+   N_("wildcards do not match `/'"), GRID+1 },
   {"wildcards-match-slash", WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
-   N_("exclude pattern wildcards match `/' (default)"), 91 },
+   N_("wildcards match `/' (default for exclusion)"), GRID+1 },
+#undef GRID
 
+#define GRID 130
   {NULL, 0, NULL, 0,
-   N_("Informative output:"), 100 },
+   N_("Informative output:"), GRID },
 
   {"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 },
+   N_("verbosely list files processed"), GRID+1 },
+  {"warning", WARNING_OPTION, N_("KEYWORD"), 0,
+   N_("warning control"), GRID+1 },
+  {"checkpoint", CHECKPOINT_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL,
+   N_("display progress messages every NUMBERth record (default 10)"),
+   GRID+1 },
+  {"checkpoint-action", CHECKPOINT_ACTION_OPTION, N_("ACTION"), 0,
+   N_("execute ACTION on each checkpoint"),
+   GRID+1 },
+  {"check-links", 'l', 0, 0,
+   N_("print a message if not all links are dumped"), GRID+1 },
+  {"totals", TOTALS_OPTION, N_("SIGNAL"), OPTION_ARG_OPTIONAL,
+   N_("print total bytes after processing the archive; "
+      "with an argument - print total bytes when this SIGNAL is delivered; "
+      "Allowed signals are: SIGHUP, SIGQUIT, SIGINT, SIGUSR1 and SIGUSR2; "
+      "the names without SIG prefix are also accepted"), GRID+1 },
   {"utc", UTC_OPTION, 0, 0,
-   N_("print file modification dates in UTC"), 102 },
+   N_("print file modification times in UTC"), GRID+1 },
+  {"full-time", FULL_TIME_OPTION, 0, 0,
+   N_("print file time to its full resolution"), GRID+1 },
   {"index-file", INDEX_FILE_OPTION, N_("FILE"), 0,
-   N_("send verbose output to FILE"), 102 },
+   N_("send verbose output to FILE"), GRID+1 },
   {"block-number", 'R', 0, 0,
-   N_("show block number within archive with each message"), 102 },
+   N_("show block number within archive with each message"), GRID+1 },
   {"interactive", 'w', 0, 0,
-   N_("ask for confirmation for every action"), 102 },
-  {"confirmation", 0, 0, OPTION_ALIAS, NULL, 102 },
+   N_("ask for confirmation for every action"), GRID+1 },
+  {"confirmation", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
   {"show-defaults", SHOW_DEFAULTS_OPTION, 0, 0,
-   N_("Show tar defaults"), 102 },
+   N_("show tar defaults"), GRID+1 },
   {"show-omitted-dirs", SHOW_OMITTED_DIRS_OPTION, 0, 0,
-   N_("When listing or extracting, list each directory that does not match search criteria"), 102 },
-
+   N_("when listing or extracting, list each directory that does not match search criteria"), GRID+1 },
+  {"show-transformed-names", SHOW_TRANSFORMED_NAMES_OPTION, 0, 0,
+   N_("show file or archive names after transformation"),
+   GRID+1 },
+  {"show-stored-names", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+  {"quoting-style", QUOTING_STYLE_OPTION, N_("STYLE"), 0,
+   N_("set name quoting style; see below for valid STYLE values"), GRID+1 },
+  {"quote-chars", QUOTE_CHARS_OPTION, N_("STRING"), 0,
+   N_("additionally quote characters from STRING"), GRID+1 },
+  {"no-quote-chars", NO_QUOTE_CHARS_OPTION, N_("STRING"), 0,
+   N_("disable quoting for characters from STRING"), GRID+1 },
+#undef GRID
+
+#define GRID 140
   {NULL, 0, NULL, 0,
-   N_("Compatibility options:"), 110 },
+   N_("Compatibility options:"), GRID },
 
   {NULL, 'o', 0, 0,
-   N_("when creating, same as --old-archive. When extracting, same as --no-same-owner"), 111 },
+   N_("when creating, same as --old-archive; when extracting, same as --no-same-owner"), GRID+1 },
+#undef GRID
 
+#define GRID 150
   {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},
+   N_("Other options:"), GRID },
+
+  {"restrict", RESTRICT_OPTION, 0, 0,
+   N_("disable use of some potentially harmful options"), -1 },
+#undef GRID
+
   {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 char const *const atime_preserve_args[] =
+{
+  "replace", "system", NULL
+};
+
+static enum atime_preserve const atime_preserve_types[] =
+{
+  replace_atime_preserve, system_atime_preserve
+};
+
+/* Make sure atime_preserve_types has as much entries as atime_preserve_args
+   (minus 1 for NULL guard) */
+ARGMATCH_VERIFY (atime_preserve_args, atime_preserve_types);
+
+/* Wildcard matching settings */
+enum wildcards
+  {
+    default_wildcards, /* For exclusion == enable_wildcards,
+                         for inclusion == disable_wildcards */
+    disable_wildcards,
+    enable_wildcards
+  };
+
+struct tar_args        /* Variables used during option parsing */
+{
+  struct textual_date *textual_date; /* Keeps the arguments to --newer-mtime
+                                       and/or --date option if they are
+                                       textual dates */
+  enum wildcards wildcards;        /* Wildcard settings (--wildcards/
+                                     --no-wildcards) */
+  int matching_flags;              /* exclude_fnmatch options */
+  int include_anchored;            /* Pattern anchoring options used for
+                                     file inclusion */
+  bool o_option;                   /* True if -o option was given */
+  bool pax_option;                 /* True if --pax-option was given */
+  char const *backup_suffix_string;   /* --suffix option argument */
+  char const *version_control_string; /* --backup option argument */
+  bool input_files;                /* True if some input files where given */
+  int compress_autodetect;         /* True if compression autodetection should
+                                     be attempted when creating archives */
+};
+
+\f
+#define MAKE_EXCL_OPTIONS(args) \
+ ((((args)->wildcards != disable_wildcards) ? EXCLUDE_WILDCARDS : 0) \
+  | (args)->matching_flags \
+  | recursion_option)
+
+#define MAKE_INCL_OPTIONS(args) \
+ ((((args)->wildcards == enable_wildcards) ? EXCLUDE_WILDCARDS : 0) \
+  | (args)->include_anchored \
+  | (args)->matching_flags \
+  | recursion_option)
+
+static char const * const vcs_file_table[] = {
+  /* CVS: */
+  "CVS",
+  ".cvsignore",
+  /* RCS: */
+  "RCS",
+  /* SCCS: */
+  "SCCS",
+  /* SVN: */
+  ".svn",
+  /* git: */
+  ".git",
+  ".gitignore",
+  /* Arch: */
+  ".arch-ids",
+  "{arch}",
+  "=RELEASE-ID",
+  "=meta-update",
+  "=update",
+  /* Bazaar */
+  ".bzr",
+  ".bzrignore",
+  ".bzrtags",
+  /* Mercurial */
+  ".hg",
+  ".hgignore",
+  ".hgtags",
+  /* darcs */
+  "_darcs",
+  NULL
+};
+
+static char const * const backup_file_table[] = {
+  ".#*",
+  "*~",
+  "#*#",
+  NULL
 };
 
 static void
-show_default_settings (FILE *stream)
+add_exclude_array (char const * const * fv)
+{
+  int i;
+
+  for (i = 0; fv[i]; i++)
+    add_exclude (excluded, fv[i], 0);
+}
+
+\f
+static char *
+format_default_settings (void)
 {
-  fprintf (stream,
-          "--format=%s -f%s -b%d --rmt-command=%s",
-          archive_format_string (DEFAULT_ARCHIVE_FORMAT),
-          DEFAULT_ARCHIVE, DEFAULT_BLOCKING,
-          DEFAULT_RMT_COMMAND);
+  return xasprintf (
+           "--format=%s -f%s -b%d --quoting-style=%s --rmt-command=%s"
 #ifdef REMOTE_SHELL
-  fprintf (stream, " --rsh-command=%s", REMOTE_SHELL);
+           " --rsh-command=%s"
 #endif
-  fprintf (stream, "\n");
+           ,
+           archive_format_string (DEFAULT_ARCHIVE_FORMAT),
+           DEFAULT_ARCHIVE, DEFAULT_BLOCKING,
+           quoting_style_args[DEFAULT_QUOTING_STYLE],
+           DEFAULT_RMT_COMMAND
+#ifdef REMOTE_SHELL
+           , REMOTE_SHELL
+#endif
+           );
 }
 
+\f
 static void
 set_subcommand_option (enum subcommand subcommand)
 {
   if (subcommand_option != UNKNOWN_SUBCOMMAND
       && subcommand_option != subcommand)
     USAGE_ERROR ((0, 0,
-                 _("You may not specify more than one `-Acdtrux' option")));
+                 _("You may not specify more than one `-Acdtrux' or `--test-label' option")));
 
   subcommand_option = subcommand;
 }
@@ -615,60 +941,165 @@ set_use_compress_program_option (const char *string)
 
   use_compress_program_option = string;
 }
+\f
+static RETSIGTYPE
+sigstat (int signo)
+{
+  compute_duration ();
+  print_total_stats ();
+#ifndef HAVE_SIGACTION
+  signal (signo, sigstat);
+#endif
+}
 
-void
-license ()
+static void
+stat_on_signal (int signo)
 {
-  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);
+#ifdef HAVE_SIGACTION
+  struct sigaction act;
+  act.sa_handler = sigstat;
+  sigemptyset (&act.sa_mask);
+  act.sa_flags = 0;
+  sigaction (signo, &act, NULL);
+#else
+  signal (signo, sigstat);
+#endif
 }
 
-static volatile int _argp_hang;
+static void
+set_stat_signal (const char *name)
+{
+  static struct sigtab
+  {
+    char const *name;
+    int signo;
+  } const sigtab[] = {
+    { "SIGUSR1", SIGUSR1 },
+    { "USR1", SIGUSR1 },
+    { "SIGUSR2", SIGUSR2 },
+    { "USR2", SIGUSR2 },
+    { "SIGHUP", SIGHUP },
+    { "HUP", SIGHUP },
+    { "SIGINT", SIGINT },
+    { "INT", SIGINT },
+    { "SIGQUIT", SIGQUIT },
+    { "QUIT", SIGQUIT }
+  };
+  struct sigtab const *p;
+
+  for (p = sigtab; p < sigtab + sizeof (sigtab) / sizeof (sigtab[0]); p++)
+    if (strcmp (p->name, name) == 0)
+      {
+       stat_on_signal (p->signo);
+       return;
+      }
+  FATAL_ERROR ((0, 0, _("Unknown signal name: %s"), name));
+}
+
+\f
+struct textual_date
+{
+  struct textual_date *next;
+  struct timespec ts;
+  const char *option;
+  char *date;
+};
+
+static int
+get_date_or_file (struct tar_args *args, const char *option,
+                 const char *str, struct timespec *ts)
+{
+  if (FILE_SYSTEM_PREFIX_LEN (str) != 0
+      || ISSLASH (*str)
+      || *str == '.')
+    {
+      struct stat st;
+      if (deref_stat (dereference_option, str, &st) != 0)
+       {
+         stat_error (str);
+         USAGE_ERROR ((0, 0, _("Date sample file not found")));
+       }
+      *ts = get_stat_mtime (&st);
+    }
+  else
+    {
+      if (! get_date (ts, str, NULL))
+       {
+         WARN ((0, 0, _("Substituting %s for unknown date format %s"),
+                tartime (*ts, false), quote (str)));
+         ts->tv_nsec = 0;
+         return 1;
+       }
+      else
+       {
+         struct textual_date *p = xmalloc (sizeof (*p));
+         p->ts = *ts;
+         p->option = option;
+         p->date = xstrdup (str);
+         p->next = args->textual_date;
+         args->textual_date = p;
+       }
+    }
+  return 0;
+}
+
+static void
+report_textual_dates (struct tar_args *args)
+{
+  struct textual_date *p;
+  for (p = args->textual_date; p; )
+    {
+      struct textual_date *next = p->next;
+      if (verbose_option)
+       {
+         char const *treated_as = tartime (p->ts, true);
+         if (strcmp (p->date, treated_as) != 0)
+           WARN ((0, 0, _("Option %s: Treating date `%s' as %s"),
+                  p->option, p->date, treated_as));
+       }
+      free (p->date);
+      free (p);
+      p = next;
+    }
+}
+
+\f
+
+/* Either NL or NUL, as decided by the --null option.  */
+static char filename_terminator;
 
 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 */
+    file_list_zero,        /* Zero separator encountered where it should not */
+    file_list_skip         /* Empty (zero-length) entry encountered, skip it */
   };
 
-/* Read from FP a sequence of characters up to FILENAME_TERMINATOR and put them
+/* Read from FP a sequence of characters up to TERM and put them
    into STK.
  */
 static enum read_file_list_state
-read_name_from_file (FILE *fp, struct obstack *stk)
+read_name_from_file (FILE *fp, struct obstack *stk, int term)
 {
   int c;
   size_t counter = 0;
 
-  for (c = getc (fp); c != EOF && c != filename_terminator; c = getc (fp))
+  for (c = getc (fp); c != EOF && c != term; 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"))); */
+         /* We have read a zero separator. The file possibly is
+            zero-separated */
          return file_list_zero;
        }
       obstack_1grow (stk, c);
       counter++;
     }
 
+  if (counter == 0 && c != EOF)
+    return file_list_skip;
+
   obstack_1grow (stk, 0);
 
   return (counter == 0 && c == EOF) ? file_list_end : file_list_success;
@@ -711,6 +1142,20 @@ add_file_id (const char *filename)
   file_id_list = p;
 }
 
+/* Default density numbers for [0-9][lmh] device specifications */
+
+#ifndef LOW_DENSITY_NUM
+# define LOW_DENSITY_NUM 0
+#endif
+
+#ifndef MID_DENSITY_NUM
+# define MID_DENSITY_NUM 8
+#endif
+
+#ifndef HIGH_DENSITY_NUM
+# define HIGH_DENSITY_NUM 16
+#endif
+
 static void
 update_argv (const char *filename, struct argp_state *state)
 {
@@ -721,6 +1166,7 @@ update_argv (const char *filename, struct argp_state *state)
   size_t new_argc;
   bool is_stdin = false;
   enum read_file_list_state read_state;
+  int term = filename_terminator;
 
   if (!strcmp (filename, "-"))
     {
@@ -735,31 +1181,44 @@ update_argv (const char *filename, struct argp_state *state)
        open_fatal (filename);
     }
 
-  while ((read_state = read_name_from_file (fp, &argv_stk)) == file_list_success)
-    count++;
-
-  if (read_state == file_list_zero)
+  while ((read_state = read_name_from_file (fp, &argv_stk, term))
+        != file_list_end)
     {
-      size_t size;
+      switch (read_state)
+       {
+       case file_list_success:
+         count++;
+         break;
 
-      WARN ((0, 0, N_("%s: file name read contains nul character"),
-            quotearg_colon (filename)));
+       case file_list_end: /* won't happen, just to pacify gcc */
+         break;
 
-      /* 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++;
+       case file_list_zero:
+         {
+           size_t size;
+
+           WARNOPT (WARN_FILENAME_WITH_NULS,
+                    (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 */
+           term = 0;
+           break;
+         }
+
+       case file_list_skip:
+         break;
+       }
     }
 
   if (!is_stdin)
@@ -770,7 +1229,7 @@ update_argv (const char *filename, struct argp_state *state)
 
   start = obstack_finish (&argv_stk);
 
-  if (filename_terminator == 0)
+  if (term == 0)
     for (p = start; *p; p += strlen (p) + 1)
       if (p[0] == '-')
        count++;
@@ -786,13 +1245,127 @@ update_argv (const char *filename, struct argp_state *state)
 
   for (i = state->next, p = start; *p; p += strlen (p) + 1, i++)
     {
-      if (filename_terminator == 0 && p[0] == '-')
+      if (term == 0 && p[0] == '-')
        state->argv[i++] = "--add-file";
       state->argv[i] = p;
     }
 }
 
 \f
+static char *
+tar_help_filter (int key, const char *text, void *input)
+{
+  struct obstack stk;
+  char *s;
+
+  switch (key)
+    {
+    default:
+      s = (char*) text;
+      break;
+
+    case 'j':
+      s = xasprintf (_("filter the archive through %s"), BZIP2_PROGRAM);
+      break;
+
+    case 'z':
+      s = xasprintf (_("filter the archive through %s"), GZIP_PROGRAM);
+      break;
+
+    case 'Z':
+      s = xasprintf (_("filter the archive through %s"), COMPRESS_PROGRAM);
+      break;
+
+    case LZIP_OPTION:
+      s = xasprintf (_("filter the archive through %s"), LZIP_PROGRAM);
+      break;
+
+    case LZMA_OPTION:
+      s = xasprintf (_("filter the archive through %s"), LZMA_PROGRAM);
+      break;
+
+    case 'J':
+      s = xasprintf (_("filter the archive through %s"), XZ_PROGRAM);
+      break;
+
+    case ARGP_KEY_HELP_EXTRA:
+      {
+       const char *tstr;
+
+       obstack_init (&stk);
+       tstr = _("Valid arguments for the --quoting-style option are:");
+       obstack_grow (&stk, tstr, strlen (tstr));
+       obstack_grow (&stk, "\n\n", 2);
+       tar_list_quoting_styles (&stk, "  ");
+       tstr = _("\n*This* tar defaults to:\n");
+       obstack_grow (&stk, tstr, strlen (tstr));
+       s = format_default_settings ();
+       obstack_grow (&stk, s, strlen (s));
+       obstack_1grow (&stk, '\n');
+       obstack_1grow (&stk, 0);
+       s = xstrdup (obstack_finish (&stk));
+       obstack_free (&stk, NULL);
+      }
+    }
+  return s;
+}
+\f
+static char *
+expand_pax_option (struct tar_args *targs, const char *arg)
+{
+  struct obstack stk;
+  char *res;
+
+  obstack_init (&stk);
+  while (*arg)
+    {
+      size_t seglen = strcspn (arg, ",");
+      char *p = memchr (arg, '=', seglen);
+      if (p)
+       {
+         size_t len = p - arg + 1;
+         obstack_grow (&stk, arg, len);
+         len = seglen - len;
+         for (++p; *p && isspace ((unsigned char) *p); p++)
+           len--;
+         if (*p == '{' && p[len-1] == '}')
+           {
+             struct timespec ts;
+             char *tmp = xmalloc (len);
+             memcpy (tmp, p + 1, len-2);
+             tmp[len-2] = 0;
+             if (get_date_or_file (targs, "--pax-option", tmp, &ts) == 0)
+               {
+                 char buf[UINTMAX_STRSIZE_BOUND], *s;
+                 s = umaxtostr (ts.tv_sec, buf);
+                 obstack_grow (&stk, s, strlen (s));
+               }
+             else
+               obstack_grow (&stk, p, len);
+             free (tmp);
+           }
+         else
+           obstack_grow (&stk, p, len);
+       }
+      else
+       obstack_grow (&stk, arg, seglen);
+
+      arg += seglen;
+      if (*arg)
+       {
+         obstack_1grow (&stk, *arg);
+         arg++;
+       }
+    }
+  obstack_1grow (&stk, 0);
+  res = xstrdup (obstack_finish (&stk));
+  obstack_free (&stk, NULL);
+  return res;
+}
+
+\f
+#define TAR_SIZE_SUFFIXES "bBcGgkKMmPTtw"
+
 static error_t
 parse_opt (int key, char *arg, struct argp_state *state)
 {
@@ -800,16 +1373,24 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
   switch (key)
     {
-      case ARGP_KEY_ARG:
-       /* File name or non-parsed option, because of ARGP_IN_ORDER */
-       name_add (arg);
-       args->input_files++;
-       break;
+    case ARGP_KEY_ARG:
+      /* File name or non-parsed option, because of ARGP_IN_ORDER */
+      name_add_name (arg, MAKE_INCL_OPTIONS (args));
+      args->input_files = true;
+      break;
 
     case 'A':
       set_subcommand_option (CAT_SUBCOMMAND);
       break;
 
+    case 'a':
+      args->compress_autodetect = true;
+      break;
+
+    case NO_AUTO_COMPRESS_OPTION:
+      args->compress_autodetect = false;
+      break;
+
     case 'b':
       {
        uintmax_t u;
@@ -838,8 +1419,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 'C':
-      name_add ("-C");
-      name_add (arg);
+      name_add_dir (arg);
       break;
 
     case 'd':
@@ -848,12 +1428,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
     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 = x2nrealloc (archive_name_array,
+                                        &allocated_archive_names,
+                                        sizeof (archive_name_array[0]));
+
       archive_name_array[archive_names++] = arg;
       break;
 
@@ -865,6 +1443,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       multi_volume_option = true;
       break;
 
+    case FULL_TIME_OPTION:
+      full_time_option = true;
+      break;
+
     case 'g':
       listed_incremental_option = arg;
       after_date_option = true;
@@ -883,6 +1465,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       dereference_option = true;
       break;
 
+    case HARD_DEREFERENCE_OPTION:
+      hard_dereference_option = true;
+      break;
+
     case 'i':
       /* Ignore zero blocks (eofs).  This can't be the default,
         because Unix tar writes two blocks of zeros, then pads out
@@ -891,14 +1477,12 @@ parse_opt (int key, char *arg, struct argp_state *state)
       ignore_zeros_option = true;
       break;
 
-    case 'I':
-      USAGE_ERROR ((0, 0,
-                   _("Warning: the -I option is not supported;"
-                     " perhaps you meant -j or -T?")));
+    case 'j':
+      set_use_compress_program_option (BZIP2_PROGRAM);
       break;
 
-    case 'j':
-      set_use_compress_program_option ("bzip2");
+    case 'J':
+      set_use_compress_program_option (XZ_PROGRAM);
       break;
 
     case 'k':
@@ -908,37 +1492,56 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
     case 'K':
       starting_file_option = true;
-      addname (arg, 0);
+      addname (arg, 0, true, NULL);
       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':
+      check_links_option = 1;
+      break;
+
     case 'L':
       {
        uintmax_t u;
-       if (xstrtoumax (arg, 0, 10, &u, "") != LONGINT_OK)
+       char *p;
+
+       if (xstrtoumax (arg, &p, 10, &u, TAR_SIZE_SUFFIXES) != LONGINT_OK)
          USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
                        _("Invalid tape length")));
-       tape_length_option = 1024 * (tarlong) u;
+       if (p > arg && !strchr (TAR_SIZE_SUFFIXES, p[-1]))
+         tape_length_option = 1024 * (tarlong) u;
+       else
+         tape_length_option = (tarlong) u;
        multi_volume_option = true;
       }
       break;
 
+    case LEVEL_OPTION:
+      {
+       char *p;
+       incremental_level = strtoul (arg, &p, 10);
+       if (*p)
+         USAGE_ERROR ((0, 0, _("Invalid incremental level value")));
+      }
+      break;
+
+    case LZIP_OPTION:
+      set_use_compress_program_option (LZIP_PROGRAM);
+      break;
+
+    case LZMA_OPTION:
+      set_use_compress_program_option (LZMA_PROGRAM);
+      break;
+
+    case LZOP_OPTION:
+      set_use_compress_program_option (LZOP_PROGRAM);
+      break;
+
     case 'm':
       touch_option = true;
       break;
@@ -950,11 +1553,19 @@ parse_opt (int key, char *arg, struct argp_state *state)
       multi_volume_option = true;
       break;
 
+    case MTIME_OPTION:
+      get_date_or_file (args, "--mtime", arg, &mtime_option);
+      set_mtime_option = true;
+      break;
+
     case 'n':
-      seekable_archive = true;
+      seek_option = 1;
+      break;
+
+    case NO_SEEK_OPTION:
+      seek_option = 0;
       break;
 
-#if !MSDOS
     case 'N':
       after_date_option = true;
       /* Fall through.  */
@@ -962,33 +1573,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
     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 == '.')
-       {
-         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;
-       }
-
+      get_date_or_file (args,
+                       key == NEWER_MTIME_OPTION ? "--newer-mtime"
+                       : "--after-date", arg, &newer_mtime_option);
       break;
-#endif /* not MSDOS */
 
     case 'o':
       args->o_option = true;
@@ -1014,7 +1602,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       /* Print block numbers for debugging bad tar archives.  */
 
       /* 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
+        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.  */
 
@@ -1022,7 +1610,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 's':
-      /* Names to extr are sorted.  */
+      /* Names to extract are sorted.  */
 
       same_order_option = true;
       break;
@@ -1031,11 +1619,31 @@ parse_opt (int key, char *arg, struct argp_state *state)
       sparse_option = true;
       break;
 
+    case SPARSE_VERSION_OPTION:
+      sparse_option = true;
+      {
+       char *p;
+       tar_sparse_major = strtoul (arg, &p, 10);
+       if (*p)
+         {
+           if (*p != '.')
+             USAGE_ERROR ((0, 0, _("Invalid sparse version value")));
+           tar_sparse_minor = strtoul (p + 1, &p, 10);
+           if (*p)
+             USAGE_ERROR ((0, 0, _("Invalid sparse version value")));
+         }
+      }
+      break;
+
     case 't':
       set_subcommand_option (LIST_SUBCOMMAND);
       verbose_option++;
       break;
 
+    case TEST_LABEL_OPTION:
+      set_subcommand_option (TEST_LABEL_SUBCOMMAND);
+      break;
+
     case 'T':
       update_argv (arg, state);
       /* Indicate we've been given -T option. This is for backward
@@ -1058,6 +1666,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
     case 'v':
       verbose_option++;
+      warning_option |= WARN_VERBOSE_WARNINGS;
       break;
 
     case 'V':
@@ -1078,7 +1687,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
     case 'X':
       if (add_exclude_file (add_exclude, excluded, arg,
-                           args->exclude_options | recursion_option, '\n')
+                           MAKE_EXCL_OPTIONS (args), '\n')
          != 0)
        {
          int e = errno;
@@ -1086,30 +1695,59 @@ parse_opt (int key, char *arg, struct argp_state *state)
        }
       break;
 
-    case 'y':
-      USAGE_ERROR ((0, 0,
-                   _("Warning: the -y option is not supported;"
-                     " perhaps you meant -j?")));
-      break;
-
     case 'z':
-      set_use_compress_program_option ("gzip");
+      set_use_compress_program_option (GZIP_PROGRAM);
       break;
 
     case 'Z':
-      set_use_compress_program_option ("compress");
+      set_use_compress_program_option (COMPRESS_PROGRAM);
       break;
 
     case ANCHORED_OPTION:
-      args->exclude_options |= EXCLUDE_ANCHORED;
+      args->matching_flags |= EXCLUDE_ANCHORED;
       break;
 
     case ATIME_PRESERVE_OPTION:
-      atime_preserve_option = true;
+      atime_preserve_option =
+       (arg
+        ? XARGMATCH ("--atime-preserve", arg,
+                     atime_preserve_args, atime_preserve_types)
+        : replace_atime_preserve);
+      if (! O_NOATIME && atime_preserve_option == system_atime_preserve)
+       FATAL_ERROR ((0, 0,
+                     _("--atime-preserve='system' is not supported"
+                       " on this platform")));
+      break;
+
+    case CHECK_DEVICE_OPTION:
+      check_device_option = true;
+      break;
+
+    case NO_CHECK_DEVICE_OPTION:
+      check_device_option = false;
       break;
 
     case CHECKPOINT_OPTION:
-      checkpoint_option = true;
+      if (arg)
+       {
+         char *p;
+
+         if (*arg == '.')
+           {
+             checkpoint_compile_action (".");
+             arg++;
+           }
+         checkpoint_option = strtoul (arg, &p, 0);
+         if (*p)
+           FATAL_ERROR ((0, 0,
+                         _("--checkpoint value is not an integer")));
+       }
+      else
+       checkpoint_option = DEFAULT_CHECKPOINT;
+      break;
+
+    case CHECKPOINT_ACTION_OPTION:
+      checkpoint_compile_action (arg);
       break;
 
     case BACKUP_OPTION:
@@ -1118,16 +1756,55 @@ parse_opt (int key, char *arg, struct argp_state *state)
        args->version_control_string = arg;
       break;
 
+    case DELAY_DIRECTORY_RESTORE_OPTION:
+      delay_directory_restore_option = true;
+      break;
+
+    case NO_DELAY_DIRECTORY_RESTORE_OPTION:
+      delay_directory_restore_option = false;
+      break;
+
     case DELETE_OPTION:
       set_subcommand_option (DELETE_SUBCOMMAND);
       break;
 
+    case EXCLUDE_BACKUPS_OPTION:
+      add_exclude_array (backup_file_table);
+      break;
+
     case EXCLUDE_OPTION:
-      add_exclude (excluded, arg, args->exclude_options | recursion_option);
+      add_exclude (excluded, arg, MAKE_EXCL_OPTIONS (args));
       break;
 
     case EXCLUDE_CACHES_OPTION:
-      exclude_caches_option = true;
+      add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_contents,
+                        cachedir_file_p);
+      break;
+
+    case EXCLUDE_CACHES_UNDER_OPTION:
+      add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_under,
+                        cachedir_file_p);
+      break;
+
+    case EXCLUDE_CACHES_ALL_OPTION:
+      add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_all,
+                        cachedir_file_p);
+      break;
+
+    case EXCLUDE_TAG_OPTION:
+      add_exclusion_tag (arg, exclusion_tag_contents, NULL);
+      break;
+
+    case EXCLUDE_TAG_UNDER_OPTION:
+      add_exclusion_tag (arg, exclusion_tag_under, NULL);
+      break;
+
+    case EXCLUDE_TAG_ALL_OPTION:
+      add_exclusion_tag (arg, exclusion_tag_all, NULL);
+      break;
+
+    case EXCLUDE_VCS_OPTION:
+      add_exclude_array (vcs_file_table);
       break;
 
     case FORCE_LOCAL_OPTION:
@@ -1143,7 +1820,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case IGNORE_CASE_OPTION:
-      args->exclude_options |= FNM_CASEFOLD;
+      args->matching_flags |= FNM_CASEFOLD;
       break;
 
     case IGNORE_COMMAND_ERROR_OPTION:
@@ -1168,7 +1845,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
            group_option = g;
          else
            FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
-                         _("%s: Invalid group")));
+                         _("Invalid group")));
        }
       break;
 
@@ -1181,11 +1858,12 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case NO_ANCHORED_OPTION:
-      args->exclude_options &= ~ EXCLUDE_ANCHORED;
+      args->include_anchored = 0; /* Clear the default for comman line args */
+      args->matching_flags &= ~ EXCLUDE_ANCHORED;
       break;
 
     case NO_IGNORE_CASE_OPTION:
-      args->exclude_options &= ~ FNM_CASEFOLD;
+      args->matching_flags &= ~ FNM_CASEFOLD;
       break;
 
     case NO_IGNORE_COMMAND_ERROR_OPTION:
@@ -1196,18 +1874,27 @@ parse_opt (int key, char *arg, struct argp_state *state)
       old_files_option = NO_OVERWRITE_DIR_OLD_FILES;
       break;
 
+    case NO_QUOTE_CHARS_OPTION:
+      for (;*arg; arg++)
+       set_char_quoting (NULL, *arg, 0);
+      break;
+
     case NO_WILDCARDS_OPTION:
-      args->exclude_options &= ~ EXCLUDE_WILDCARDS;
+      args->wildcards = disable_wildcards;
       break;
 
     case NO_WILDCARDS_MATCH_SLASH_OPTION:
-      args->exclude_options |= FNM_FILE_NAME;
+      args->matching_flags |= FNM_FILE_NAME;
       break;
 
     case NULL_OPTION:
       filename_terminator = '\0';
       break;
 
+    case NO_NULL_OPTION:
+      filename_terminator = '\n';
+      break;
+
     case NUMERIC_OWNER_OPTION:
       numeric_owner_option = true;
       break;
@@ -1226,6 +1913,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
        }
       break;
 
+    case OVERWRITE_DIR_OPTION:
+      old_files_option = DEFAULT_OLD_FILES;
+      break;
+
     case OVERWRITE_OPTION:
       old_files_option = OVERWRITE_OLD_FILES;
       break;
@@ -1244,9 +1935,22 @@ parse_opt (int key, char *arg, struct argp_state *state)
        }
       break;
 
+    case QUOTE_CHARS_OPTION:
+      for (;*arg; arg++)
+       set_char_quoting (NULL, *arg, 1);
+      break;
+
+    case QUOTING_STYLE_OPTION:
+      tar_set_quoting_style (arg);
+      break;
+
     case PAX_OPTION:
-      args->pax_option++;
-      xheader_set_option (arg);
+      {
+       char *tmp = expand_pax_option (args, arg);
+       args->pax_option = true;
+       xheader_set_option (tmp);
+       free (tmp);
+      }
       break;
 
     case POSIX_OPTION:
@@ -1254,14 +1958,18 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case PRESERVE_OPTION:
+      /* FIXME: What it is good for? */
       same_permissions_option = true;
       same_order_option = true;
+      WARN ((0, 0, _("The --preserve option is deprecated, "
+                    "use --preserve-permissions --preserve-order instead")));
       break;
 
     case RECORD_SIZE_OPTION:
       {
        uintmax_t u;
-       if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+
+       if (! (xstrtoumax (arg, NULL, 10, &u, TAR_SIZE_SUFFIXES) == LONGINT_OK
               && u == (size_t) u))
          USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
                        _("Invalid record size")));
@@ -1281,6 +1989,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       remove_files_option = true;
       break;
 
+    case RESTRICT_OPTION:
+      restrict_option = true;
+      break;
+
     case RMT_COMMAND_OPTION:
       rmt_command = arg;
       break;
@@ -1290,8 +2002,13 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case SHOW_DEFAULTS_OPTION:
-      show_default_settings (stdout);
-      exit(0);
+      {
+       char *s = format_default_settings ();
+       printf ("%s\n", s);
+       close_stdout ();
+       free (s);
+       exit (0);
+      }
 
     case STRIP_COMPONENTS_OPTION:
       {
@@ -1308,6 +2025,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       show_omitted_dirs_option = true;
       break;
 
+    case SHOW_TRANSFORMED_NAMES_OPTION:
+      show_transformed_names_option = true;
+      break;
+
     case SUFFIX_OPTION:
       backup_option = true;
       args->backup_suffix_string = arg;
@@ -1320,10 +2041,17 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case TOTALS_OPTION:
-      totals_option = true;
+      if (arg)
+       set_stat_signal (arg);
+      else
+       totals_option = true;
+      break;
+
+    case TRANSFORM_OPTION:
+      set_transform_expr (arg);
       break;
 
-    case USE_COMPRESS_PROGRAM_OPTION:
+    case 'I':
       set_use_compress_program_option (arg);
       break;
 
@@ -1332,15 +2060,11 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case WILDCARDS_OPTION:
-      args->exclude_options |= EXCLUDE_WILDCARDS;
+      args->wildcards = enable_wildcards;
       break;
 
     case WILDCARDS_MATCH_SLASH_OPTION:
-      args->exclude_options &= ~ FNM_FILE_NAME;
-      break;
-
-    case CHECK_LINKS_OPTION:
-      check_links_option = 1;
+      args->matching_flags &= ~ FNM_FILE_NAME;
       break;
 
     case NO_RECURSION_OPTION:
@@ -1371,6 +2095,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       unquote_option = false;
       break;
 
+    case WARNING_OPTION:
+      set_warning_option (arg);
+      break;
+
     case '0':
     case '1':
     case '2':
@@ -1402,25 +2130,15 @@ parse_opt (int key, char *arg, struct argp_state *state)
        switch (arg[0])
          {
          case 'l':
-#ifdef LOW_NUM
-           device += LOW_NUM;
-#endif
+           device += LOW_DENSITY_NUM;
            break;
 
          case 'm':
-#ifdef MID_NUM
-           device += MID_NUM;
-#else
-           device += 8;
-#endif
+           device += MID_DENSITY_NUM;
            break;
 
          case 'h':
-#ifdef HGH_NUM
-           device += HGH_NUM;
-#else
-           device += 16;
-#endif
+           device += HIGH_DENSITY_NUM;
            break;
 
          default:
@@ -1431,12 +2149,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
 #endif /* not DENSITY_LETTER */
 
        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 = x2nrealloc (archive_name_array,
+                                          &allocated_archive_names,
+                                          sizeof (archive_name_array[0]));
        archive_name_array[archive_names++] = xstrdup (buf);
       }
       break;
@@ -1448,36 +2163,6 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
 #endif /* not DEVICE_PREFIX */
 
-    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 VERSION_OPTION:
-      fprintf (state->out_stream, "%s\n", argp_program_version);
-      exit (0);
-
-    case LICENSE_OPTION:
-      license ();
-      break;
-
-    case HANG_OPTION:
-      _argp_hang = atoi (arg ? arg : "3600");
-      while (_argp_hang-- > 0)
-       sleep (1);
-      break;
-
     default:
       return ARGP_ERR_UNKNOWN;
     }
@@ -1490,7 +2175,7 @@ static struct argp argp = {
   N_("[FILE]..."),
   doc,
   NULL,
-  NULL,
+  tar_help_filter,
   NULL
 };
 
@@ -1498,39 +2183,51 @@ void
 usage (int status)
 {
   argp_help (&argp, stderr, ARGP_HELP_SEE, (char*) program_name);
+  close_stdout ();
   exit (status);
 }
 
 /* Parse the options for tar.  */
 
 static struct argp_option *
-find_argp_option (struct argp_option *options, int letter)
+find_argp_option (struct argp_option *o, 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;
+       !(o->name == NULL
+        && o->key == 0
+        && o->arg == 0
+        && o->flags == 0
+        && o->doc == NULL); o++)
+    if (o->key == letter)
+      return o;
   return NULL;
 }
 
+static const char *tar_authors[] = {
+  "John Gilmore",
+  "Jay Fenlason",
+  NULL
+};
+
 static void
 decode_options (int argc, char **argv)
 {
-  int index;
+  int idx;
   struct tar_args args;
 
+  argp_version_setup ("tar", tar_authors);
+
   /* Set some default option values.  */
-  args.textual_date_option = NULL;
-  args.exclude_options = EXCLUDE_WILDCARDS;
-  args.o_option = 0;
-  args.pax_option = 0;
+  args.textual_date = NULL;
+  args.wildcards = default_wildcards;
+  args.matching_flags = 0;
+  args.include_anchored = EXCLUDE_ANCHORED;
+  args.o_option = false;
+  args.pax_option = false;
   args.backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
   args.version_control_string = 0;
-  args.input_files = 0;
+  args.input_files = false;
+  args.compress_autodetect = false;
 
   subcommand_option = UNKNOWN_SUBCOMMAND;
   archive_format = DEFAULT_FORMAT;
@@ -1541,10 +2238,18 @@ decode_options (int argc, char **argv)
   newer_mtime_option.tv_nsec = -1;
   recursion_option = FNM_LEADING_DIR;
   unquote_option = true;
+  tar_sparse_major = 1;
+  tar_sparse_minor = 0;
 
   owner_option = -1;
   group_option = -1;
 
+  check_device_option = true;
+
+  incremental_level = -1;
+
+  seek_option = -1;
+
   /* Convert old-style tar call by exploding option element and rearranging
      options accordingly.  */
 
@@ -1606,9 +2311,8 @@ decode_options (int argc, char **argv)
 
   prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
 
-  if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_HELP,
-                 &index, &args))
-    exit (1);
+  if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &idx, &args))
+    exit (TAREXIT_FAILURE);
 
 
   /* Special handling for 'o' option:
@@ -1635,12 +2339,16 @@ decode_options (int argc, char **argv)
     }
 
   /* Handle operands after any "--" argument.  */
-  for (; index < argc; index++)
+  for (; idx < argc; idx++)
     {
-      name_add (argv[index]);
-      args.input_files++;
+      name_add_name (argv[idx], MAKE_INCL_OPTIONS (&args));
+      args.input_files = true;
     }
 
+  /* Warn about implicit use of the wildcards in command line arguments.
+     See TODO */
+  warn_regex_usage = args.wildcards == default_wildcards;
+
   /* Derive option values and check option consistency.  */
 
   if (archive_format == DEFAULT_FORMAT)
@@ -1651,15 +2359,10 @@ decode_options (int argc, char **argv)
        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)
-    assert_format (FORMAT_MASK (OLDGNU_FORMAT) | FORMAT_MASK (GNU_FORMAT));
-
-  if (sparse_option)
+  if ((volume_label_option && subcommand_option == CREATE_SUBCOMMAND)
+      || incremental_option
+      || multi_volume_option
+      || sparse_option)
     assert_format (FORMAT_MASK (OLDGNU_FORMAT)
                   | FORMAT_MASK (GNU_FORMAT)
                   | FORMAT_MASK (POSIX_FORMAT));
@@ -1677,18 +2380,6 @@ decode_options (int argc, char **argv)
                          _("--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
@@ -1710,25 +2401,33 @@ decode_options (int argc, char **argv)
       && NEWER_OPTION_INITIALIZED (newer_mtime_option))
     USAGE_ERROR ((0, 0,
                  _("Cannot combine --listed-incremental with --newer")));
+  if (incremental_level != -1 && !listed_incremental_option)
+    WARN ((0, 0,
+          _("--level is meaningless without --listed-incremental")));
 
   if (volume_label_option)
     {
-      size_t volume_label_max_len =
-       (sizeof current_header->header.name
-        - 1 /* for trailing '\0' */
-        - (multi_volume_option
-           ? (sizeof " Volume "
-              - 1 /* for null at end of " Volume " */
-              + INT_STRLEN_BOUND (int) /* for volume number */
-              - 1 /* for sign, as 0 <= volno */)
-           : 0));
-      if (volume_label_max_len < strlen (volume_label_option))
-       USAGE_ERROR ((0, 0,
-                     ngettext ("%s: Volume label is too long (limit is %lu byte)",
-                               "%s: Volume label is too long (limit is %lu bytes)",
-                               volume_label_max_len),
-                     quotearg_colon (volume_label_option),
-                     (unsigned long) volume_label_max_len));
+      if (archive_format == GNU_FORMAT || archive_format == OLDGNU_FORMAT)
+       {
+         size_t volume_label_max_len =
+           (sizeof current_header->header.name
+            - 1 /* for trailing '\0' */
+            - (multi_volume_option
+               ? (sizeof " Volume "
+                  - 1 /* for null at end of " Volume " */
+                  + INT_STRLEN_BOUND (int) /* for volume number */
+                  - 1 /* for sign, as 0 <= volno */)
+               : 0));
+         if (volume_label_max_len < strlen (volume_label_option))
+           USAGE_ERROR ((0, 0,
+                         ngettext ("%s: Volume label is too long (limit is %lu byte)",
+                                   "%s: Volume label is too long (limit is %lu bytes)",
+                                   volume_label_max_len),
+                         quotearg_colon (volume_label_option),
+                         (unsigned long) volume_label_max_len));
+       }
+      /* else FIXME
+        Label length in PAX format is limited by the volume size. */
     }
 
   if (verify_option)
@@ -1743,8 +2442,12 @@ decode_options (int argc, char **argv)
     {
       if (multi_volume_option)
        USAGE_ERROR ((0, 0, _("Cannot use multi-volume compressed archives")));
-      if (subcommand_option == UPDATE_SUBCOMMAND)
+      if (subcommand_option == UPDATE_SUBCOMMAND
+         || subcommand_option == APPEND_SUBCOMMAND
+         || subcommand_option == DELETE_SUBCOMMAND)
        USAGE_ERROR ((0, 0, _("Cannot update compressed archives")));
+      if (subcommand_option == CAT_SUBCOMMAND)
+       USAGE_ERROR ((0, 0, _("Cannot concatenate compressed archives")));
     }
 
   /* It is no harm to use --pax-option on non-pax archives in archive
@@ -1762,23 +2465,55 @@ decode_options (int argc, char **argv)
   if (recursive_unlink_option)
     old_files_option = UNLINK_FIRST_OLD_FILES;
 
-  if (utc_option)
+  /* Flags for accessing files to be read from or copied into.  POSIX says
+     O_NONBLOCK has unspecified effect on most types of files, but in
+     practice it never harms and sometimes helps.  */
+  {
+    int base_open_flags =
+      (O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
+       | (dereference_option ? 0 : O_NOFOLLOW)
+       | (atime_preserve_option == system_atime_preserve ? O_NOATIME : 0));
+    open_read_flags = O_RDONLY | base_open_flags;
+    open_searchdir_flags = O_SEARCH | O_DIRECTORY | base_open_flags;
+  }
+  fstatat_flags = dereference_option ? 0 : AT_SYMLINK_NOFOLLOW;
+
+  if (subcommand_option == TEST_LABEL_SUBCOMMAND)
+    {
+      /* --test-label is silent if the user has specified the label name to
+        compare against. */
+      if (!args.input_files)
+       verbose_option++;
+    }
+  else if (utc_option)
     verbose_option = 2;
 
+  if (tape_length_option && tape_length_option < record_size)
+    USAGE_ERROR ((0, 0, _("Volume length cannot be less than record size")));
+
+  if (same_order_option && listed_incremental_option)
+    USAGE_ERROR ((0, 0, _("--preserve-order is not compatible with "
+                         "--listed-incremental")));
+
   /* Forbid using -c with no input files whatsoever.  Check that `-f -',
      explicit or implied, is used correctly.  */
 
   switch (subcommand_option)
     {
     case CREATE_SUBCOMMAND:
-      if (args.input_files == 0 && !files_from_option)
+      if (!args.input_files && !files_from_option)
        USAGE_ERROR ((0, 0,
                      _("Cowardly refusing to create an empty archive")));
+      if (args.compress_autodetect && archive_names
+         && strcmp (archive_name_array[0], "-"))
+       set_compression_program_by_suffix (archive_name_array[0],
+                                          use_compress_program_option);
       break;
 
     case EXTRACT_SUBCOMMAND:
     case LIST_SUBCOMMAND:
     case DIFF_SUBCOMMAND:
+    case TEST_LABEL_SUBCOMMAND:
       for (archive_name_cursor = archive_name_array;
           archive_name_cursor < archive_name_array + archive_names;
           archive_name_cursor++)
@@ -1800,6 +2535,16 @@ decode_options (int argc, char **argv)
       break;
     }
 
+  /* Initialize stdlis */
+  if (index_file_name)
+    {
+      stdlis = fopen (index_file_name, "w");
+      if (! stdlis)
+       open_error (index_file_name);
+    }
+  else
+    stdlis = to_stdout_option ? stderr : stdout;
+
   archive_name_cursor = archive_name_array;
 
   /* Prepare for generating backup names.  */
@@ -1816,13 +2561,9 @@ decode_options (int argc, char **argv)
        backup_option = false;
     }
 
-  if (verbose_option && args.textual_date_option)
-    {
-      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"),
-              args.textual_date_option, treated_as));
-    }
+  checkpoint_finish_compile ();
+
+  report_textual_dates (&args);
 }
 
 \f
@@ -1833,15 +2574,19 @@ int
 main (int argc, char **argv)
 {
   set_start_time ();
-  program_name = argv[0];
+  set_program_name (argv[0]);
 
   setlocale (LC_ALL, "");
   bindtextdomain (PACKAGE, LOCALEDIR);
   textdomain (PACKAGE);
 
+  exit_failure = TAREXIT_FAILURE;
   exit_status = TAREXIT_SUCCESS;
   filename_terminator = '\n';
-  set_quoting_style (0, escape_quoting_style);
+  set_quoting_style (0, DEFAULT_QUOTING_STYLE);
+
+  /* Make sure we have first three descriptors available */
+  stdopen ();
 
   /* Pre-allocate a few structures.  */
 
@@ -1852,16 +2597,16 @@ main (int argc, char **argv)
 
   obstack_init (&argv_stk);
 
-#ifdef SIGCHLD
   /* System V fork+wait does not work if SIGCHLD is ignored.  */
   signal (SIGCHLD, SIG_DFL);
-#endif
 
-  init_names ();
+  /* Try to disable the ability to unlink a directory.  */
+  priv_set_remove_linkdir ();
 
   /* Decode options.  */
 
   decode_options (argc, argv);
+
   name_init ();
 
   /* Main command execution.  */
@@ -1873,7 +2618,7 @@ main (int argc, char **argv)
     {
     case UNKNOWN_SUBCOMMAND:
       USAGE_ERROR ((0, 0,
-                   _("You must specify one of the `-Acdtrux' options")));
+                   _("You must specify one of the `-Acdtrux' or `--test-label'  options")));
 
     case CAT_SUBCOMMAND:
     case UPDATE_SUBCOMMAND:
@@ -1887,8 +2632,6 @@ main (int argc, char **argv)
 
     case CREATE_SUBCOMMAND:
       create_archive ();
-      if (totals_option)
-       print_total_written ();
       break;
 
     case EXTRACT_SUBCOMMAND:
@@ -1909,8 +2652,14 @@ main (int argc, char **argv)
       diff_init ();
       read_and (diff_archive);
       break;
+
+    case TEST_LABEL_SUBCOMMAND:
+      test_archive_label ();
     }
 
+  if (totals_option)
+    print_total_stats ();
+
   if (check_links_option)
     check_links ();
 
@@ -1922,12 +2671,14 @@ main (int argc, char **argv)
   free (archive_name_array);
   name_term ();
 
-  if (stdlis != stderr && (ferror (stdlis) || fclose (stdlis) != 0))
-    FATAL_ERROR ((0, 0, _("Error in writing to standard output")));
   if (exit_status == TAREXIT_FAILURE)
-    error (0, 0, _("Error exit delayed from previous errors"));
-  if (ferror (stderr) || fclose (stderr) != 0)
-    exit_status = TAREXIT_FAILURE;
+    error (0, 0, _("Exiting with failure status due to previous errors"));
+
+  if (stdlis == stdout)
+    close_stdout ();
+  else if (ferror (stderr) || fclose (stderr) != 0)
+    set_exit_status (TAREXIT_FAILURE);
+
   return exit_status;
 }
 
@@ -1937,14 +2688,62 @@ tar_stat_init (struct tar_stat_info *st)
   memset (st, 0, sizeof (*st));
 }
 
+/* Close the stream or file descriptor associated with ST, and remove
+   all traces of it from ST.  Return true if successful, false (with a
+   diagnostic) otherwise.  */
+bool
+tar_stat_close (struct tar_stat_info *st)
+{
+  int status = (st->dirstream ? closedir (st->dirstream)
+               : 0 < st->fd ? close (st->fd)
+               : 0);
+  st->dirstream = 0;
+  st->fd = 0;
+
+  if (status == 0)
+    return true;
+  else
+    {
+      close_diag (st->orig_file_name);
+      return false;
+    }
+}
+
 void
 tar_stat_destroy (struct tar_stat_info *st)
 {
+  tar_stat_close (st);
   free (st->orig_file_name);
   free (st->file_name);
   free (st->link_name);
   free (st->uname);
   free (st->gname);
   free (st->sparse_map);
+  free (st->dumpdir);
+  xheader_destroy (&st->xhdr);
   memset (st, 0, sizeof (*st));
 }
+
+/* Format mask for all available formats that support nanosecond
+   timestamp resolution. */
+#define NS_PRECISION_FORMAT_MASK FORMAT_MASK (POSIX_FORMAT)
+
+/* Same as timespec_cmp, but ignore nanoseconds if current archive
+   format does not provide sufficient resolution.  */
+int
+tar_timespec_cmp (struct timespec a, struct timespec b)
+{
+  if (!(FORMAT_MASK (current_format) & NS_PRECISION_FORMAT_MASK))
+    a.tv_nsec = b.tv_nsec = 0;
+  return timespec_cmp (a, b);
+}
+
+/* Set tar exit status to VAL, unless it is already indicating
+   a more serious condition. This relies on the fact that the
+   values of TAREXIT_ constants are ranged by severity. */
+void
+set_exit_status (int val)
+{
+  if (val > exit_status)
+    exit_status = val;
+}
This page took 0.084713 seconds and 4 git commands to generate.