]> Dogcows Code - chaz/tar/blobdiff - src/tar.c
Carefully crafted invalid headers can cause buffer overrun.
[chaz/tar] / src / tar.c
index b70ce1b63782e4fc43e2c39ccfcd7d1d9f847046..b0e039dbc7ff28d6970103fd4d5dabd9801d09db 100644 (file)
--- a/src/tar.c
+++ b/src/tar.c
@@ -1,5 +1,8 @@
 /* A tar (tape archiver) program.
-   Copyright (C) 1988, 92,93,94,95,96,97, 1999 Free Software Foundation, Inc.
+
+   Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000,
+   2001, 2003, 2004, 2005 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
 
    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 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 <getopt.h>
+#include <fnmatch.h>
+#include <argp.h>
+
+#include <signal.h>
+#if ! defined SIGCHLD && defined SIGCLD
+# define SIGCHLD SIGCLD
+#endif
 
 /* The following causes "common.h" to produce definitions of all the global
    variables, rather than just "extern" declarations of them.  GNU tar does
    depend on the system loader to preset all GLOBAL variables to neutral (or
-   zero) values, explicit initialisation is usually not done.  */
+   zero) values; explicit initialization is usually not done.  */
 #define GLOBAL
 #include "common.h"
 
-#include "backupfile.h"
-enum backup_type get_version ();
-
-/* FIXME: We should use a conversion routine that does reasonable error
-   checking -- atol doesn't.  For now, punt.  */
-#define intconv        atol
-
-time_t get_date ();
+#include <getdate.h>
+#include <localedir.h>
+#include <rmt.h>
+#include <prepargs.h>
+#include <quotearg.h>
+#include <xstrtol.h>
 
 /* Local declarations.  */
 
+#ifndef DEFAULT_ARCHIVE_FORMAT
+# define DEFAULT_ARCHIVE_FORMAT GNU_FORMAT
+#endif
+
 #ifndef DEFAULT_ARCHIVE
 # define DEFAULT_ARCHIVE "tar.out"
 #endif
@@ -46,59 +57,13 @@ time_t get_date ();
 # define DEFAULT_BLOCKING 20
 #endif
 
-static void usage PARAMS ((int));
 \f
 /* Miscellaneous.  */
 
-/*------------------------------------------------------------------------.
-| Check if STRING0 is the decimal representation of number, and store its |
-| value.  If not a decimal number, return 0.                             |
-`------------------------------------------------------------------------*/
-
-static int
-check_decimal (const char *string0, uintmax_t *result)
-{
-  const char *string = string0;
-  uintmax_t value = 0;
-
-  do
-    switch (*string)
-      {
-      case '0':
-      case '1':
-      case '2':
-      case '3':
-      case '4':
-      case '5':
-      case '6':
-      case '7':
-      case '8':
-      case '9':
-       {
-         uintmax_t v10 = value * 10;
-         uintmax_t v10d = v10 + (*string - '0');
-         if (v10 / 10 != value || v10d < v10)
-           return 0;
-         value = v10d;
-       }
-       break;
-
-      default:
-       return 0;
-      }
-  while (*++string);
-
-  *result = value;
-  return 1;
-}
-
-/*----------------------------------------------.
-| Doesn't return if stdin already requested.    |
-`----------------------------------------------*/
-
 /* Name of option using stdin.  */
-static const char *stdin_used_by = NULL;
+static const char *stdin_used_by;
 
+/* Doesn't return if stdin already requested.  */
 void
 request_stdin (const char *option)
 {
@@ -109,43 +74,100 @@ request_stdin (const char *option)
   stdin_used_by = option;
 }
 
-/*--------------------------------------------------------.
-| Returns true if and only if the user typed 'y' or 'Y'.  |
-`--------------------------------------------------------*/
-
+/* Returns true if and only if the user typed 'y' or 'Y'.  */
 int
 confirm (const char *message_action, const char *message_name)
 {
-  static FILE *confirm_file = NULL;
+  static FILE *confirm_file;
+  static int confirm_file_EOF;
 
   if (!confirm_file)
     {
       if (archive == 0 || stdin_used_by)
-       confirm_file = fopen (TTY_NAME, "r");
+       {
+         confirm_file = fopen (TTY_NAME, "r");
+         if (! confirm_file)
+           open_fatal (TTY_NAME);
+       }
       else
        {
          request_stdin ("-w");
          confirm_file = stdin;
        }
-
-      if (!confirm_file)
-       FATAL_ERROR ((0, 0, _("Cannot read confirmation from user")));
     }
 
-  fprintf (stdlis, "%s %s?", message_action, message_name);
+  fprintf (stdlis, "%s %s?", message_action, quote (message_name));
   fflush (stdlis);
 
   {
-    int reply = getc (confirm_file);
+    int reply = confirm_file_EOF ? EOF : getc (confirm_file);
     int character;
 
     for (character = reply;
-        character != '\n' && character != EOF;
+        character != '\n';
         character = getc (confirm_file))
-      continue;
+      if (character == EOF)
+       {
+         confirm_file_EOF = 1;
+         fputc ('\n', stdlis);
+         fflush (stdlis);
+         break;
+       }
     return reply == 'y' || reply == 'Y';
   }
 }
+
+static struct fmttab {
+  char const *name;
+  enum archive_format fmt;
+} const fmttab[] = {
+  { "v7",      V7_FORMAT },
+  { "oldgnu",  OLDGNU_FORMAT },
+  { "ustar",   USTAR_FORMAT },
+  { "posix",   POSIX_FORMAT },
+#if 0 /* not fully supported yet */
+  { "star",    STAR_FORMAT },
+#endif
+  { "gnu",     GNU_FORMAT },
+  { "pax",     POSIX_FORMAT }, /* An alias for posix */
+  { NULL,       0 }
+};
+
+static void
+set_archive_format (char const *name)
+{
+  struct fmttab const *p;
+
+  for (p = fmttab; strcmp (p->name, name) != 0; )
+    if (! (++p)->name)
+      USAGE_ERROR ((0, 0, _("%s: Invalid archive format"),
+                   quotearg_colon (name)));
+
+  archive_format = p->fmt;
+}
+
+static const char *
+archive_format_string (enum archive_format fmt)
+{
+  struct fmttab const *p;
+
+  for (p = fmttab; p->name; p++)
+    if (p->fmt == fmt)
+      return p->name;
+  return "unknown?";
+}
+
+#define FORMAT_MASK(n) (1<<(n))
+
+static void
+assert_format(unsigned fmt_mask)
+{
+  if ((FORMAT_MASK(archive_format) & fmt_mask) == 0)
+    USAGE_ERROR ((0, 0,
+                 _("GNU features wanted on incompatible archive format")));
+}
+
+
 \f
 /* Options.  */
 
@@ -153,316 +175,425 @@ confirm (const char *message_action, const char *message_name)
    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 nongraphic characters as pseudo short option
-   characters, starting at 2 and going upwards.  */
-
-#define BACKUP_OPTION                  2
-#define DELETE_OPTION                  3
-#define EXCLUDE_OPTION                 4
-#define GROUP_OPTION                   5
-#define MODE_OPTION                    6
-#define NEWER_MTIME_OPTION             7
-#define NO_RECURSE_OPTION              8
-#define NULL_OPTION                    9
-#define OWNER_OPTION                   10
-#define POSIX_OPTION                   11
-#define PRESERVE_OPTION                        12
-#define RECORD_SIZE_OPTION             13
-#define RSH_COMMAND_OPTION             14
-#define SUFFIX_OPTION                  15
-#define USE_COMPRESS_PROGRAM_OPTION    16
-#define VOLNO_FILE_OPTION              17
-
-/* Some cleanup is being made in GNU tar long options.  Using old names is
-   allowed for a while, but will also send a warning to stderr.  Take old
-   names out in 1.14, or in summer 1997, whichever happens last.  We use
-   nongraphic characters as pseudo short option characters, starting at 31
-   and going downwards.  */
-
-#define OBSOLETE_ABSOLUTE_NAMES                31
-#define OBSOLETE_BLOCK_COMPRESS                30
-#define OBSOLETE_BLOCKING_FACTOR       29
-#define OBSOLETE_BLOCK_NUMBER          28
-#define OBSOLETE_READ_FULL_RECORDS     27
-#define OBSOLETE_TOUCH                 26
-#define OBSOLETE_VERSION_CONTROL       25
-
-/* If nonzero, display usage information and exit.  */
-static int show_help = 0;
-
-/* If nonzero, print the version on standard output and exit.  */
-static int show_version = 0;
-
-struct option long_options[] =
+   short option, we use non-characters as pseudo short options,
+   starting at CHAR_MAX + 1 and going upwards.  */
+
+enum
 {
-  {"absolute-names", no_argument, NULL, 'P'},
-  {"absolute-paths", no_argument, NULL, OBSOLETE_ABSOLUTE_NAMES},
-  {"after-date", required_argument, NULL, 'N'},
-  {"append", no_argument, NULL, 'r'},
-  {"atime-preserve", no_argument, &atime_preserve_option, 1},
-  {"backup", optional_argument, NULL, BACKUP_OPTION},
-  {"block-compress", no_argument, NULL, OBSOLETE_BLOCK_COMPRESS},
-  {"block-number", no_argument, NULL, 'R'},
-  {"block-size", required_argument, NULL, OBSOLETE_BLOCKING_FACTOR},
-  {"blocking-factor", required_argument, NULL, 'b'},
-  {"bunzip2", no_argument, NULL, 'y'},
-  {"bzip2", no_argument, NULL, 'y'},
-  {"catenate", no_argument, NULL, 'A'},
-  {"checkpoint", no_argument, &checkpoint_option, 1},
-  {"compare", no_argument, NULL, 'd'},
-  {"compress", no_argument, NULL, 'Z'},
-  {"concatenate", no_argument, NULL, 'A'},
-  {"confirmation", no_argument, NULL, 'w'},
-  /* FIXME: --selective as a synonym for --confirmation?  */
-  {"create", no_argument, NULL, 'c'},
-  {"delete", no_argument, NULL, DELETE_OPTION},
-  {"dereference", no_argument, NULL, 'h'},
-  {"diff", no_argument, NULL, 'd'},
-  {"directory", required_argument, NULL, 'C'},
-  {"ending-file", required_argument, NULL, 'E'},
-  {"exclude", required_argument, NULL, EXCLUDE_OPTION},
-  {"exclude-from", required_argument, NULL, 'X'},
-  {"extract", no_argument, NULL, 'x'},
-  {"file", required_argument, NULL, 'f'},
-  {"files-from", required_argument, NULL, 'T'},
-  {"force-local", no_argument, &force_local_option, 1},
-  {"get", no_argument, NULL, 'x'},
-  {"group", required_argument, NULL, GROUP_OPTION},
-  {"gunzip", no_argument, NULL, 'z'},
-  {"gzip", no_argument, NULL, 'z'},
-  {"help", no_argument, &show_help, 1},
-  {"ignore-failed-read", no_argument, &ignore_failed_read_option, 1},
-  {"ignore-zeros", no_argument, NULL, 'i'},
-  /* FIXME: --ignore-end as a new name for --ignore-zeros?  */
-  {"incremental", no_argument, NULL, 'G'},
-  {"info-script", required_argument, NULL, 'F'},
-  {"interactive", no_argument, NULL, 'w'},
-  {"keep-old-files", no_argument, NULL, 'k'},
-  {"label", required_argument, NULL, 'V'},
-  {"list", no_argument, NULL, 't'},
-  {"listed-incremental", required_argument, NULL, 'g'},
-  {"mode", required_argument, NULL, MODE_OPTION},
-  {"modification-time", no_argument, NULL, OBSOLETE_TOUCH},
-  {"multi-volume", no_argument, NULL, 'M'},
-  {"new-volume-script", required_argument, NULL, 'F'},
-  {"newer", required_argument, NULL, 'N'},
-  {"newer-mtime", required_argument, NULL, NEWER_MTIME_OPTION},
-  {"null", no_argument, NULL, NULL_OPTION},
-  {"no-recursion", no_argument, NULL, NO_RECURSE_OPTION},
-  {"numeric-owner", no_argument, &numeric_owner_option, 1},
-  {"old-archive", no_argument, NULL, 'o'},
-  {"one-file-system", no_argument, NULL, 'l'},
-  {"owner", required_argument, NULL, OWNER_OPTION},
-  {"portability", no_argument, NULL, 'o'},
-  {"posix", no_argument, NULL, POSIX_OPTION},
-  {"preserve", no_argument, NULL, PRESERVE_OPTION},
-  {"preserve-order", no_argument, NULL, 's'},
-  {"preserve-permissions", no_argument, NULL, 'p'},
-  {"recursive-unlink", no_argument, &recursive_unlink_option, 1},
-  {"read-full-blocks", no_argument, NULL, OBSOLETE_READ_FULL_RECORDS},
-  {"read-full-records", no_argument, NULL, 'B'},
-  /* FIXME: --partial-blocks might be a synonym for --read-full-records?  */
-  {"record-number", no_argument, NULL, OBSOLETE_BLOCK_NUMBER},
-  {"record-size", required_argument, NULL, RECORD_SIZE_OPTION},
-  {"remove-files", no_argument, &remove_files_option, 1},
-  {"rsh-command", required_argument, NULL, RSH_COMMAND_OPTION},
-  {"same-order", no_argument, NULL, 's'},
-  {"same-owner", no_argument, &same_owner_option, 1},
-  {"same-permissions", no_argument, NULL, 'p'},
-  {"show-omitted-dirs", no_argument, &show_omitted_dirs_option, 1},
-  {"sparse", no_argument, NULL, 'S'},
-  {"starting-file", required_argument, NULL, 'K'},
-  {"suffix", required_argument, NULL, SUFFIX_OPTION},
-  {"tape-length", required_argument, NULL, 'L'},
-  {"to-stdout", no_argument, NULL, 'O'},
-  {"totals", no_argument, &totals_option, 1},
-  {"touch", no_argument, NULL, 'm'},
-  {"uncompress", no_argument, NULL, 'Z'},
-  {"ungzip", no_argument, NULL, 'z'},
-  {"unlink-first", no_argument, NULL, 'U'},
-  {"update", no_argument, NULL, 'u'},
-  {"use-compress-program", required_argument, NULL, USE_COMPRESS_PROGRAM_OPTION},
-  {"verbose", no_argument, NULL, 'v'},
-  {"verify", no_argument, NULL, 'W'},
-  {"version", no_argument, &show_version, 1},
-  {"version-control", required_argument, NULL, OBSOLETE_VERSION_CONTROL},
-  {"volno-file", required_argument, NULL, VOLNO_FILE_OPTION},
-
-  {0, 0, 0, 0}
+  ANCHORED_OPTION = CHAR_MAX + 1,
+  ATIME_PRESERVE_OPTION,
+  BACKUP_OPTION,
+  CHECKPOINT_OPTION,
+  CHECK_LINKS_OPTION,
+  DELETE_OPTION,
+  EXCLUDE_OPTION,
+  EXCLUDE_CACHES_OPTION,
+  FORCE_LOCAL_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_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
 };
 
-/*---------------------------------------------.
-| Print a usage message and exit with STATUS.  |
-`---------------------------------------------*/
-
-static 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"), 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 overwrite existing files when extracting\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   handle new GNU-format incremental backup\n\
-      --ignore-failed-read   do not exit with nonzero on unreadable files\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\
-      --numeric-owner          always use numbers for user/group names\n\
-  -p, --same-permissions       extract all protection 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\
-  -V, --label=NAME                   create archive with volume name NAME\n\
-              PATTERN                at list/extract time, a globbing PATTERN\n\
-  -o, --old-archive, --portability   write a V7 format archive\n\
-      --posix                        write a POSIX conformant archive\n\
-  -y, --bzip2, --bunzip2             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 globbing PATTERN\n\
-  -X, --exclude-from=FILE      exclude globbing patterns listed in FILE\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\
-  -E, --ending-file=NAME       end reading the archive before file NAME\n\
-  -K, --starting-file=NAME     begin at file NAME in the archive\n"),
-            stdout);
-#if !MSDOS
-      fputs (_("\
-  -N, --newer=DATE             only store files newer than DATE\n\
-      --newer-mtime            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 removel, 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\
-      --totals          print total bytes written while creating archive\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\
-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\
+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\
+  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\
-GNU tar cannot read nor produce `--posix' archives.  If POSIXLY_CORRECT\n\
-is set in the environment, GNU extensions are disallowed with `--posix'.\n\
-Support for POSIX is only partially implemented, don't count on it yet.\n\
-ARCHIVE may be FILE, HOST:FILE or USER@HOST:FILE; and FILE may be a file\n\
-or a device.  *This* `tar' defaults to `-f%s -b%d'.\n"),
-             DEFAULT_ARCHIVE, DEFAULT_BLOCKING);
-      fputs (_("\
-\n\
-Report bugs to <tar-bugs@gnu.ai.mit.edu>.\n"),
-              stdout);
-    }
-  exit (status);
-}
+  never, simple   always make simple backups\n");
+
 
-/*----------------------------.
-| Parse the options for tar.  |
-`----------------------------*/
+/* NOTE:
 
-/* Available option letters are DHIJQY and aejnqy.  Some are reserved:
+   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 (--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:E:F:GK:L:MN:OPRST:UV:WX:Zb:cdf:g:hiklmoprstuvwxyz"
+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)
@@ -478,33 +609,942 @@ 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;
+  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
+add_file_id (const char *filename)
+{
+  struct file_id_list *p;
+  struct stat st;
+
+  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;
+}
+
+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);
+    }
+
+  while ((read_state = read_name_from_file (fp, &argv_stk)) == file_list_success)
+    count++;
+
+  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 (!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++)
+    {
+      if (filename_terminator == 0 && p[0] == '-')
+       state->argv[i++] = "--add-file";
+      state->argv[i] = p;
+    }
+}
+
+\f
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  struct tar_args *args = state->input;
+
+  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 'A':
+      set_subcommand_option (CAT_SUBCOMMAND);
+      break;
+
+    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;
+
+    case 'B':
+      /* Try to reblock input records.  For reading 4.2BSD pipes.  */
+
+      /* 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)
+       {
+         allocated_archive_names *= 2;
+         archive_name_array =
+           xrealloc (archive_name_array,
+                     sizeof (const char *) * allocated_archive_names);
+       }
+      archive_name_array[archive_names++] = arg;
+      break;
+
+    case 'F':
+      /* Since -F is only useful with -M, make it implied.  Run this
+        script at the end of each tape.  */
+
+      info_script_option = arg;
+      multi_volume_option = true;
+      break;
+
+    case 'g':
+      listed_incremental_option = arg;
+      after_date_option = true;
+      /* Fall through.  */
+
+    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.  */
+
+      incremental_option = true;
+      break;
+
+    case 'h':
+      /* Follow symbolic links.  */
+      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
+        the record with garbage.  */
+
+      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':
+      {
+       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 'm':
+      touch_option = true;
+      break;
+
+    case 'M':
+      /* Make multivolume archive: when we can't write any more into
+        the archive, re-open it, and continue writing.  */
+
+      multi_volume_option = true;
+      break;
+
+    case 'n':
+      seekable_archive = true;
+      break;
+
+#if !MSDOS
+    case 'N':
+      after_date_option = true;
+      /* Fall through.  */
+
+    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;
+       }
+
+      break;
+#endif /* not MSDOS */
+
+    case 'o':
+      args->o_option = true;
+      break;
+
+    case 'O':
+      to_stdout_option = true;
+      break;
+
+    case 'p':
+      same_permissions_option = true;
+      break;
+
+    case 'P':
+      absolute_names_option = true;
+      break;
+
+    case 'r':
+      set_subcommand_option (APPEND_SUBCOMMAND);
+      break;
+
+    case 'R':
+      /* 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
+        BSD-derived systems.  This is a consequence of the block/record
+        terminology confusion.  */
+
+      block_number_option = true;
+      break;
+
+    case 's':
+      /* Names to extr are sorted.  */
+
+      same_order_option = true;
+      break;
+
+    case 'S':
+      sparse_option = true;
+      break;
+
+    case 't':
+      set_subcommand_option (LIST_SUBCOMMAND);
+      verbose_option++;
+      break;
+
+    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;
+
+    case 'u':
+      set_subcommand_option (UPDATE_SUBCOMMAND);
+      break;
+
+    case 'U':
+      old_files_option = UNLINK_FIRST_OLD_FILES;
+      break;
+
+    case UTC_OPTION:
+      utc_option = true;
+      break;
+
+    case 'v':
+      verbose_option++;
+      break;
+
+    case 'V':
+      volume_label_option = arg;
+      break;
+
+    case 'w':
+      interactive_option = true;
+      break;
+
+    case 'W':
+      verify_option = true;
+      break;
+
+    case 'x':
+      set_subcommand_option (EXTRACT_SUBCOMMAND);
+      break;
+
+    case 'X':
+      if (add_exclude_file (add_exclude, excluded, arg,
+                           args->exclude_options | recursion_option, '\n')
+         != 0)
+       {
+         int e = errno;
+         FATAL_ERROR ((0, e, "%s", quotearg_colon (arg)));
+       }
+      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");
+      break;
+
+    case 'Z':
+      set_use_compress_program_option ("compress");
+      break;
+
+    case ANCHORED_OPTION:
+      args->exclude_options |= EXCLUDE_ANCHORED;
+      break;
+
+    case ATIME_PRESERVE_OPTION:
+      atime_preserve_option = true;
+      break;
+
+    case CHECKPOINT_OPTION:
+      checkpoint_option = true;
+      break;
+
+    case BACKUP_OPTION:
+      backup_option = true;
+      if (arg)
+       args->version_control_string = arg;
+      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 NO_WILDCARDS_MATCH_SLASH_OPTION:
+      args->exclude_options |= FNM_FILE_NAME;
+      break;
+
+    case NULL_OPTION:
+      filename_terminator = '\0';
+      break;
+
+    case NUMERIC_OWNER_OPTION:
+      numeric_owner_option = true;
+      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 OVERWRITE_OPTION:
+      old_files_option = OVERWRITE_OLD_FILES;
+      break;
+
+    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;
+
+    case PAX_OPTION:
+      args->pax_option++;
+      xheader_set_option (arg);
+      break;
+
+    case POSIX_OPTION:
+      set_archive_format ("posix");
+      break;
+
+    case PRESERVE_OPTION:
+      same_permissions_option = true;
+      same_order_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 RECURSIVE_UNLINK_OPTION:
+      recursive_unlink_option = true;
+      break;
+
+    case REMOVE_FILES_OPTION:
+      remove_files_option = true;
+      break;
+
+    case RMT_COMMAND_OPTION:
+      rmt_command = arg;
+      break;
+
+    case RSH_COMMAND_OPTION:
+      rsh_command_option = arg;
+      break;
+
+    case SHOW_DEFAULTS_OPTION:
+      show_default_settings (stdout);
+      exit(0);
+
+    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 SHOW_OMITTED_DIRS_OPTION:
+      show_omitted_dirs_option = true;
+      break;
+
+    case SUFFIX_OPTION:
+      backup_option = true;
+      args->backup_suffix_string = arg;
+      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 TOTALS_OPTION:
+      totals_option = true;
+      break;
+
+    case USE_COMPRESS_PROGRAM_OPTION:
+      set_use_compress_program_option (arg);
+      break;
+
+    case VOLNO_FILE_OPTION:
+      volno_file_option = arg;
+      break;
+
+    case WILDCARDS_OPTION:
+      args->exclude_options |= EXCLUDE_WILDCARDS;
+      break;
+
+    case WILDCARDS_MATCH_SLASH_OPTION:
+      args->exclude_options &= ~ FNM_FILE_NAME;
+      break;
+
+    case CHECK_LINKS_OPTION:
+      check_links_option = 1;
+      break;
+
+    case NO_RECURSION_OPTION:
+      recursion_option = 0;
+      break;
+
+    case NO_SAME_OWNER_OPTION:
+      same_owner_option = -1;
+      break;
+
+    case NO_SAME_PERMISSIONS_OPTION:
+      same_permissions_option = -1;
+      break;
+
+    case RECURSION_OPTION:
+      recursion_option = FNM_LEADING_DIR;
+      break;
+
+    case SAME_OWNER_OPTION:
+      same_owner_option = 1;
+      break;
+
+    case UNQUOTE_OPTION:
+      unquote_option = true;
+      break;
+
+    case NO_UNQUOTE_OPTION:
+      unquote_option = false;
+      break;
+
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+
+#ifdef DEVICE_PREFIX
+      {
+       int device = key - '0';
+       int density;
+       static char buf[sizeof DEVICE_PREFIX + 10];
+       char *cursor;
+
+       if (arg[1])
+         argp_error (state, _("Malformed density argument: %s"), quote (arg));
+
+       strcpy (buf, DEVICE_PREFIX);
+       cursor = buf + strlen (buf);
+
+#ifdef DENSITY_LETTER
+
+       sprintf (cursor, "%d%c", device, arg[0]);
+
+#else /* not DENSITY_LETTER */
+
+       switch (arg[0])
+         {
+         case 'l':
+#ifdef LOW_NUM
+           device += LOW_NUM;
+#endif
+           break;
+
+         case 'm':
+#ifdef MID_NUM
+           device += MID_NUM;
+#else
+           device += 8;
+#endif
+           break;
+
+         case 'h':
+#ifdef HGH_NUM
+           device += HGH_NUM;
+#else
+           device += 16;
+#endif
+           break;
+
+         default:
+           argp_error (state, _("Unknown density: `%c'"), arg[0]);
+         }
+       sprintf (cursor, "%d", device);
+
+#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[archive_names++] = xstrdup (buf);
+      }
+      break;
+
+#else /* not DEVICE_PREFIX */
+
+      argp_error (state,
+                 _("Options `-[0-7][lmh]' not supported by *this* tar"));
+
+#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;
+    }
+  return 0;
+}
+
+static struct argp argp = {
+  options,
+  parse_opt,
+  N_("[FILE]..."),
+  doc,
+  NULL,
+  NULL,
+  NULL
+};
+
+void
+usage (int status)
+{
+  argp_help (&argp, stderr, ARGP_HELP_SEE, (char*) program_name);
+  exit (status);
+}
+
+/* Parse the options for tar.  */
+
+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;
 }
 
 static void
-decode_options (int argc, char *const *argv)
+decode_options (int argc, char **argv)
 {
-  int optchar;                 /* option letter */
-  int input_files;             /* number of input files */
-  const char *backup_suffix_string;
-  const char *version_control_string;
+  int index;
+  struct tar_args args;
 
   /* 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;
 
   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;
 
   owner_option = -1;
   group_option = -1;
 
-  backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
-  version_control_string = getenv ("VERSION_CONTROL");
-
   /* Convert old-style tar call by exploding option element and rearranging
      options accordingly.  */
 
@@ -516,7 +1556,6 @@ decode_options (int argc, char *const *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 */
 
       /* Initialize a constructed option.  */
 
@@ -526,7 +1565,7 @@ decode_options (int argc, char *const *argv)
       /* Allocate a new argument array, and copy program name in it.  */
 
       new_argc = argc - 1 + strlen (argv[1]);
-      new_argv = (char **) xmalloc (new_argc * sizeof (char *));
+      new_argv = xmalloc ((new_argc + 1) * sizeof (char *));
       in = argv;
       out = new_argv;
       *out++ = *in++;
@@ -536,10 +1575,12 @@ decode_options (int argc, char *const *argv)
 
       for (letter = *in++; *letter; letter++)
        {
+         struct argp_option *opt;
+
          buffer[1] = *letter;
          *out++ = xstrdup (buffer);
-         cursor = strchr (OPTION_STRING, *letter);
-         if (cursor && cursor[1] == ':')
+         opt = find_argp_option (options, *letter);
+         if (opt && opt->arg)
            {
              if (in < argv + argc)
                *out++ = *in++;
@@ -553,6 +1594,7 @@ decode_options (int argc, char *const *argv)
 
       while (in < argv + argc)
        *out++ = *in++;
+      *out = 0;
 
       /* Replace the old option list by the new one.  */
 
@@ -562,510 +1604,91 @@ decode_options (int argc, char *const *argv)
 
   /* Parse all options and non-options as they appear.  */
 
-  input_files = 0;
-
-  while (optchar = getopt_long (argc, argv, OPTION_STRING, long_options, NULL),
-        optchar != EOF)
-    switch (optchar)
-      {
-      case '?':
-       usage (TAREXIT_FAILURE);
-
-      case 0:
-       break;
-
-      case 1:
-       /* File name or non-parsed option, because of RETURN_IN_ORDER
-          ordering triggerred by the leading dash in OPTION_STRING.  */
-
-       name_add (optarg);
-       input_files++;
-       break;
-
-      case 'A':
-       set_subcommand_option (CAT_SUBCOMMAND);
-       break;
-
-      case OBSOLETE_BLOCK_COMPRESS:
-       WARN ((0, 0, _("Obsolete option, now implied by --blocking-factor")));
-       break;
-
-      case OBSOLETE_BLOCKING_FACTOR:
-       WARN ((0, 0, _("Obsolete option name replaced by --blocking-factor")));
-       /* Fall through.  */
-
-      case 'b':
-       blocking_factor = intconv (optarg);
-       record_size = blocking_factor * (size_t) BLOCKSIZE;
-       break;
-
-      case OBSOLETE_READ_FULL_RECORDS:
-       WARN ((0, 0,
-              _("Obsolete option name replaced by --read-full-records")));
-       /* Fall through.  */
-
-      case 'B':
-       /* Try to reblock input records.  For reading 4.2BSD pipes.  */
-
-       /* 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.  */
-
-       read_full_records_option = 1;
-       break;
-
-      case 'c':
-       set_subcommand_option (CREATE_SUBCOMMAND);
-       break;
-
-      case 'C':
-       name_add ("-C");
-       name_add (optarg);
-       break;
-
-      case 'd':
-       set_subcommand_option (DIFF_SUBCOMMAND);
-       break;
-
-      case 'E':
-       ending_file_option = optarg;
-       break;
-
-      case 'f':
-       if (archive_names == allocated_archive_names)
-         {
-           allocated_archive_names *= 2;
-           archive_name_array = (const char **)
-             xrealloc (archive_name_array,
-                       sizeof (const char *) * allocated_archive_names);
-         }
-       archive_name_array[archive_names++] = optarg;
-       break;
-
-      case 'F':
-       /* Since -F is only useful with -M, make it implied.  Run this
-          script at the end of each tape.  */
-
-       info_script_option = optarg;
-       multi_volume_option = 1;
-       break;
-
-      case 'g':
-       listed_incremental_option = optarg;
-       /* Fall through.  */
-
-      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.  */
-
-       incremental_option = 1;
-       break;
-
-      case 'h':
-       /* Follow symbolic links.  */
-
-       dereference_option = 1;
-       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.  */
-
-       ignore_zeros_option = 1;
-       break;
-
-      case 'k':
-       /* Don't overwrite existing files.  */
-
-       keep_old_files_option = 1;
-       break;
-
-      case 'K':
-       starting_file_option = 1;
-       addname (optarg);
-       break;
-
-      case 'l':
-       /* When dumping directories, don't dump files/subdirectories
-          that are on other filesystems.  */
-
-       one_file_system_option = 1;
-       break;
-
-      case 'L':
-       clear_tarlong (tape_length_option);
-       add_to_tarlong (tape_length_option, intconv (optarg));
-       mult_tarlong (tape_length_option, 1024);
-       multi_volume_option = 1;
-       break;
-
-      case OBSOLETE_TOUCH:
-       WARN ((0, 0, _("Obsolete option name replaced by --touch")));
-       /* Fall through.  */
-
-      case 'm':
-       touch_option = 1;
-       break;
-
-      case 'M':
-       /* Make multivolume archive: when we can't write any more into
-          the archive, re-open it, and continue writing.  */
-
-       multi_volume_option = 1;
-       break;
-
-#if !MSDOS
-      case 'N':
-       after_date_option = 1;
-       /* Fall through.  */
-
-      case NEWER_MTIME_OPTION:
-       if (newer_mtime_option)
-         USAGE_ERROR ((0, 0, _("More than one threshold date")));
-
-       newer_mtime_option = get_date (optarg, (voidstar) 0);
-       if (newer_mtime_option == (time_t) -1)
-         USAGE_ERROR ((0, 0, _("Invalid date format `%s'"), optarg));
-
-       break;
-#endif /* not MSDOS */
-
-      case 'o':
-       if (archive_format == DEFAULT_FORMAT)
-         archive_format = V7_FORMAT;
-       else if (archive_format != V7_FORMAT)
-         USAGE_ERROR ((0, 0, _("Conflicting archive format options")));
-       break;
-
-      case 'O':
-       to_stdout_option = 1;
-       break;
-
-      case 'p':
-       same_permissions_option = 1;
-       break;
-
-      case OBSOLETE_ABSOLUTE_NAMES:
-       WARN ((0, 0, _("Obsolete option name replaced by --absolute-names")));
-       /* Fall through.  */
-
-      case 'P':
-       absolute_names_option = 1;
-       break;
-
-      case 'r':
-       set_subcommand_option (APPEND_SUBCOMMAND);
-       break;
-
-      case OBSOLETE_BLOCK_NUMBER:
-       WARN ((0, 0, _("Obsolete option name replaced by --block-number")));
-       /* Fall through.  */
-
-      case 'R':
-       /* 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
-          BSD-derived systems.  This is a consequence of the block/record
-          terminology confusion.  */
-
-       block_number_option = 1;
-       break;
-
-      case 's':
-       /* Names to extr are sorted.  */
-
-       same_order_option = 1;
-       break;
-
-      case 'S':
-       sparse_option = 1;
-       break;
-
-      case 't':
-       set_subcommand_option (LIST_SUBCOMMAND);
-       verbose_option++;
-       break;
-
-      case 'T':
-       files_from_option = optarg;
-       break;
-
-      case 'u':
-       set_subcommand_option (UPDATE_SUBCOMMAND);
-       break;
-
-      case 'U':
-       unlink_first_option = 1;
-       break;
-
-      case 'v':
-       verbose_option++;
-       break;
-
-      case 'V':
-       volume_label_option = optarg;
-       break;
-
-      case 'w':
-       interactive_option = 1;
-       break;
-
-      case 'W':
-       verify_option = 1;
-       break;
-
-      case 'x':
-       set_subcommand_option (EXTRACT_SUBCOMMAND);
-       break;
-
-      case 'X':
-       exclude_option = 1;
-       add_exclude_file (optarg);
-       break;
-
-      case 'y':
-       set_use_compress_program_option ("bzip2");
-       break;
-
-      case 'z':
-       set_use_compress_program_option ("gzip");
-       break;
-
-      case 'Z':
-       set_use_compress_program_option ("compress");
-       break;
-
-      case OBSOLETE_VERSION_CONTROL:
-       WARN ((0, 0, _("Obsolete option name replaced by --backup")));
-       /* Fall through.  */
-
-      case BACKUP_OPTION:
-       backup_option = 1;
-       if (optarg)
-         version_control_string = optarg;
-       break;
-
-      case DELETE_OPTION:
-       set_subcommand_option (DELETE_SUBCOMMAND);
-       break;
-
-      case EXCLUDE_OPTION:
-       exclude_option = 1;
-       add_exclude (optarg);
-       break;
-
-      case GROUP_OPTION:
-       if (! (strlen (optarg) < GNAME_FIELD_SIZE
-              && gname_to_gid (optarg, &group_option)))
-         {
-           uintmax_t g;
-           if (!check_decimal (optarg, &g) || g != (gid_t) g)
-             ERROR ((TAREXIT_FAILURE, 0, _("Invalid group given on option")));
-           else
-             group_option = g;
-         }
-       break;
-
-      case MODE_OPTION:
-       mode_option
-         = mode_compile (optarg,
-                         MODE_MASK_EQUALS | MODE_MASK_PLUS | MODE_MASK_MINUS);
-       if (mode_option == MODE_INVALID)
-         ERROR ((TAREXIT_FAILURE, 0, _("Invalid mode given on option")));
-       if (mode_option == MODE_MEMORY_EXHAUSTED)
-         ERROR ((TAREXIT_FAILURE, 0, _("Memory exhausted")));
-       break;
-
-      case NO_RECURSE_OPTION:
-       no_recurse_option = 1;
-       break;
-
-      case NULL_OPTION:
-       filename_terminator = '\0';
-       break;
-
-      case OWNER_OPTION:
-       if (! (strlen (optarg) < UNAME_FIELD_SIZE
-              && uname_to_uid (optarg, &owner_option)))
-         {
-           uintmax_t u;
-           if (!check_decimal (optarg, &u) || u != (uid_t) u)
-             ERROR ((TAREXIT_FAILURE, 0, _("Invalid owner given on option")));
-           else
-             owner_option = u;
-         }
-       break;
-
-      case POSIX_OPTION:
-#if OLDGNU_COMPATIBILITY
-       if (archive_format == DEFAULT_FORMAT)
-         archive_format = GNU_FORMAT;
-       else if (archive_format != GNU_FORMAT)
-         USAGE_ERROR ((0, 0, _("Conflicting archive format options")));
-#else
-       if (archive_format == DEFAULT_FORMAT)
-         archive_format = POSIX_FORMAT;
-       else if (archive_format != POSIX_FORMAT)
-         USAGE_ERROR ((0, 0, _("Conflicting archive format options")));
-#endif
-       break;
-
-      case PRESERVE_OPTION:
-       same_permissions_option = 1;
-       same_order_option = 1;
-       break;
-
-      case RECORD_SIZE_OPTION:
-       record_size = intconv (optarg);
-       if (record_size % BLOCKSIZE != 0)
-         USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."),
-                       BLOCKSIZE));
-       blocking_factor = record_size / BLOCKSIZE;
-       break;
+  prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
 
-      case RSH_COMMAND_OPTION:
-       rsh_command_option = optarg;
-       break;
+  if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_HELP,
+                 &index, &args))
+    exit (1);
 
-      case SUFFIX_OPTION:
-       backup_option = 1;
-       backup_suffix_string = optarg;
-       break;
 
-      case VOLNO_FILE_OPTION:
-       volno_file_option = optarg;
-       break;
+  /* Special handling for 'o' option:
 
-      case USE_COMPRESS_PROGRAM_OPTION:
-       set_use_compress_program_option (optarg);
-       break;
+     GNU tar used to say "output old format".
+     UNIX98 tar says don't chown files after extracting (we use
+     "--no-same-owner" for this).
 
-      case '0':
-      case '1':
-      case '2':
-      case '3':
-      case '4':
-      case '5':
-      case '6':
-      case '7':
+     The old GNU tar semantics is retained when used with --create
+     option, otherwise UNIX98 semantics is assumed */
 
-#ifdef DEVICE_PREFIX
+  if (args.o_option)
+    {
+      if (subcommand_option == CREATE_SUBCOMMAND)
        {
-         int device = optchar - '0';
-         int density;
-         static char buf[sizeof DEVICE_PREFIX + 10];
-         char *cursor;
-
-         density = getopt_long (argc, argv, "lmh", NULL, NULL);
-         strcpy (buf, DEVICE_PREFIX);
-         cursor = buf + strlen (buf);
-
-#ifdef DENSITY_LETTER
-
-         sprintf (cursor, "%d%c", device, density);
-
-#else /* not DENSITY_LETTER */
-
-         switch (density)
-           {
-           case 'l':
-#ifdef LOW_NUM
-             device += LOW_NUM;
-#endif
-             break;
-
-           case 'm':
-#ifdef MID_NUM
-             device += MID_NUM;
-#else
-             device += 8;
-#endif
-             break;
-
-           case 'h':
-#ifdef HGH_NUM
-             device += HGH_NUM;
-#else
-             device += 16;
-#endif
-             break;
-
-           default:
-             usage (TAREXIT_FAILURE);
-           }
-         sprintf (cursor, "%d", device);
+         /* GNU Tar <= 1.13 compatibility */
+         set_archive_format ("v7");
+       }
+      else
+       {
+         /* UNIX98 compatibility */
+         same_owner_option = -1;
+       }
+    }
 
-#endif /* not DENSITY_LETTER */
+  /* Handle operands after any "--" argument.  */
+  for (; index < argc; index++)
+    {
+      name_add (argv[index]);
+      args.input_files++;
+    }
 
-         if (archive_names == allocated_archive_names)
-           {
-             allocated_archive_names *= 2;
-             archive_name_array = (const char **)
-               xrealloc (archive_name_array,
-                         sizeof (const char *) * allocated_archive_names);
-           }
-         archive_name_array[archive_names++] = buf;
+  /* Derive option values and check option consistency.  */
 
-         /* FIXME: How comes this works for many archives when buf is
-            not xstrdup'ed?  */
-       }
-       break;
+  if (archive_format == DEFAULT_FORMAT)
+    {
+      if (args.pax_option)
+       archive_format = POSIX_FORMAT;
+      else
+       archive_format = DEFAULT_ARCHIVE_FORMAT;
+    }
 
-#else /* not DEVICE_PREFIX */
+  if (volume_label_option && subcommand_option == CREATE_SUBCOMMAND)
+    assert_format (FORMAT_MASK (OLDGNU_FORMAT)
+                  | FORMAT_MASK (GNU_FORMAT));
 
-       USAGE_ERROR ((0, 0,
-                     _("Options `-[0-7][lmh]' not supported by *this* tar")));
 
-#endif /* not DEVICE_PREFIX */
-      }
+  if (incremental_option || multi_volume_option)
+    assert_format (FORMAT_MASK (OLDGNU_FORMAT) | FORMAT_MASK (GNU_FORMAT));
 
-  /* Process trivial options.  */
+  if (sparse_option)
+    assert_format (FORMAT_MASK (OLDGNU_FORMAT)
+                  | FORMAT_MASK (GNU_FORMAT)
+                  | FORMAT_MASK (POSIX_FORMAT));
 
-  if (show_version)
+  if (occurrence_option)
     {
-      printf ("tar (GNU %s) %s\n", PACKAGE, VERSION);
-      fputs (_("\
-\n\
-Copyright (C) 1988, 92, 93, 94, 95, 96, 97 Free Software Foundation, Inc.\n"),
-            stdout);
-      fputs (_("\
-This is free software; see the source for copying conditions.  There is NO\n\
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"),
-            stdout);
-      fputs (_("\
-\n\
-Written by John Gilmore and Jay Fenlason.\n"),
-            stdout);
-      exit (TAREXIT_SUCCESS);
+      if (!args.input_files)
+       USAGE_ERROR ((0, 0,
+                     _("--occurrence is meaningless without a file list")));
+      if (subcommand_option != DELETE_SUBCOMMAND
+         && subcommand_option != DIFF_SUBCOMMAND
+         && subcommand_option != EXTRACT_SUBCOMMAND
+         && subcommand_option != LIST_SUBCOMMAND)
+           USAGE_ERROR ((0, 0,
+                         _("--occurrence cannot be used in the requested operation mode")));
     }
 
-  if (show_help)
-    usage (TAREXIT_SUCCESS);
-
-  /* Derive option values and check option consistency.  */
-
-  if (archive_format == DEFAULT_FORMAT)
+  if (seekable_archive && subcommand_option == DELETE_SUBCOMMAND)
     {
-#if OLDGNU_COMPATIBILITY
-      archive_format = OLDGNU_FORMAT;
-#else
-      archive_format = GNU_FORMAT;
-#endif
+      /* 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_format == GNU_FORMAT && getenv ("POSIXLY_CORRECT"))
-    archive_format = POSIX_FORMAT;
-
-  if ((volume_label_option != NULL
-       || incremental_option || multi_volume_option || sparse_option)
-      && archive_format != OLDGNU_FORMAT && archive_format != GNU_FORMAT)
-    USAGE_ERROR ((0, 0,
-                 _("GNU features wanted on incompatible archive format")));
-
   if (archive_names == 0)
     {
       /* If no archive file name given, try TAPE from the environment, or
@@ -1073,7 +1696,7 @@ Written by John Gilmore and Jay Fenlason.\n"),
 
       archive_names = 1;
       archive_name_array[0] = getenv ("TAPE");
-      if (archive_name_array[0] == NULL)
+      if (! archive_name_array[0])
        archive_name_array[0] = DEFAULT_ARCHIVE;
     }
 
@@ -1081,11 +1704,66 @@ Written by John Gilmore and Jay Fenlason.\n"),
 
   if (archive_names > 1 && !multi_volume_option)
     USAGE_ERROR ((0, 0,
-                 _("Multiple archive files requires `-M' option")));
+                 _("Multiple archive files require `-M' option")));
+
+  if (listed_incremental_option
+      && NEWER_OPTION_INITIALIZED (newer_mtime_option))
+    USAGE_ERROR ((0, 0,
+                 _("Cannot combine --listed-incremental with --newer")));
+
+  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 (verify_option)
+    {
+      if (multi_volume_option)
+       USAGE_ERROR ((0, 0, _("Cannot verify multi-volume archives")));
+      if (use_compress_program_option)
+       USAGE_ERROR ((0, 0, _("Cannot verify compressed archives")));
+    }
+
+  if (use_compress_program_option)
+    {
+      if (multi_volume_option)
+       USAGE_ERROR ((0, 0, _("Cannot use multi-volume compressed archives")));
+      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)
-    unlink_first_option = 1;
+    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.  */
@@ -1093,7 +1771,7 @@ Written by John Gilmore and Jay Fenlason.\n"),
   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;
@@ -1126,43 +1804,65 @@ Written by John Gilmore and Jay Fenlason.\n"),
 
   /* 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 = get_version (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 && 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));
+    }
 }
+
 \f
 /* Tar proper.  */
 
-/*-----------------------.
-| Main routine for tar.         |
-`-----------------------*/
-
+/* Main routine for tar.  */
 int
-main (int argc, char *const *argv)
+main (int argc, char **argv)
 {
+  set_start_time ();
   program_name = argv[0];
+
   setlocale (LC_ALL, "");
   bindtextdomain (PACKAGE, LOCALEDIR);
   textdomain (PACKAGE);
 
   exit_status = TAREXIT_SUCCESS;
   filename_terminator = '\n';
+  set_quoting_style (0, escape_quoting_style);
 
   /* Pre-allocate a few structures.  */
 
   allocated_archive_names = 10;
-  archive_name_array = (const char **)
+  archive_name_array =
     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);
+#endif
+
   init_names ();
 
   /* Decode options.  */
 
   decode_options (argc, argv);
-  name_init (argc, argv);
+  name_init ();
 
   /* Main command execution.  */
 
@@ -1186,12 +1886,7 @@ main (int argc, char *const *argv)
       break;
 
     case CREATE_SUBCOMMAND:
-      if (totals_option)
-       init_total_written ();
-
       create_archive ();
-      name_close ();
-
       if (totals_option)
        print_total_written ();
       break;
@@ -1199,6 +1894,11 @@ main (int argc, char *const *argv)
     case EXTRACT_SUBCOMMAND:
       extr_init ();
       read_and (extract_archive);
+
+      /* FIXME: should extract_finish () even if an ordinary signal is
+        received.  */
+      extract_finish ();
+
       break;
 
     case LIST_SUBCOMMAND:
@@ -1211,6 +1911,9 @@ main (int argc, char *const *argv)
       break;
     }
 
+  if (check_links_option)
+    check_links ();
+
   if (volno_file_option)
     closeout_volume_number ();
 
@@ -1219,7 +1922,29 @@ main (int argc, char *const *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"));
-  exit (exit_status);
+  if (ferror (stderr) || fclose (stderr) != 0)
+    exit_status = TAREXIT_FAILURE;
+  return exit_status;
+}
+
+void
+tar_stat_init (struct tar_stat_info *st)
+{
+  memset (st, 0, sizeof (*st));
+}
+
+void
+tar_stat_destroy (struct tar_stat_info *st)
+{
+  free (st->orig_file_name);
+  free (st->file_name);
+  free (st->link_name);
+  free (st->uname);
+  free (st->gname);
+  free (st->sparse_map);
+  memset (st, 0, sizeof (*st));
 }
This page took 0.0776 seconds and 4 git commands to generate.