]> Dogcows Code - chaz/tar/blobdiff - src/tar.c
tar: live within system-supplied limits on file descriptors
[chaz/tar] / src / tar.c
index f5926ed6b050bf3c21044783a6ebc6cbd08d2664..6fd117c636702f564511be0d3c7c4876b849efdb 100644 (file)
--- a/src/tar.c
+++ b/src/tar.c
-/* Tar -- a tape archiver.
-   Copyright (C) 1988, 1992, 1993 Free Software Foundation
+/* A tar (tape archiver) program.
 
-This file is part of GNU Tar.
+   Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000,
+   2001, 2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
 
-GNU Tar 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 version.
+   Written by John Gilmore, starting 1985-08-25.
 
-GNU Tar is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+   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 3, or (at your option) any later
+   version.
 
-You should have received a copy of the GNU General Public License
-along with GNU Tar; see the file COPYING.  If not, write to
-the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+   Public License for more details.
 
-/*
- * A tar (tape archiver) program.
- *
- * Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85.
- */
+   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.,
+   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
 
-#include <stdio.h>
-#include <sys/types.h>         /* Needed for typedefs in tar.h */
-#include "getopt.h"
-#include "regex.h"
+#include <system.h>
 
-/*
- * The following causes "tar.h" to produce definitions of all the
- * global variables, rather than just "extern" declarations of them.
- */
-#define TAR_EXTERN             /**/
-#include "tar.h"
+#include <fnmatch.h>
+#include <argp.h>
+#include <argp-namefrob.h>
+#include <argp-fmtstream.h>
+#include <argp-version-etc.h>
 
-#include "port.h"
-#include "fnmatch.h"
+#include <signal.h>
+#if ! defined SIGCHLD && defined SIGCLD
+# define SIGCHLD SIGCLD
+#endif
 
-/*
- * We should use a conversion routine that does reasonable error
- * checking -- atoi doesn't.  For now, punt.  FIXME.
- */
-#define intconv        atoi
-PTR ck_malloc ();
-PTR ck_realloc ();
-extern int getoldopt ();
-extern void read_and ();
-extern void list_archive ();
-extern void extract_archive ();
-extern void diff_archive ();
-extern void create_archive ();
-extern void update_archive ();
-extern void junk_archive ();
-extern void init_volume_number ();
-extern void closeout_volume_number ();
-
-/* JF */
-extern time_t get_date ();
-
-time_t new_time;
-
-static FILE *namef;            /* File to read names from */
-static char **n_argv;          /* Argv used by name routines */
-static int n_argc;             /* Argc used by name routines */
-static char **n_ind;           /* Store an array of names */
-static int n_indalloc;         /* How big is the array? */
-static int n_indused;          /* How many entries does it have? */
-static int n_indscan;          /* How many of the entries have we scanned? */
-
-
-extern FILE *msg_file;
-
-int check_exclude ();
-void add_exclude ();
-void add_exclude_file ();
-void addname ();
-void describe ();
-void diff_init ();
-void extr_init ();
-int is_regex ();
-void name_add ();
-void name_init ();
-void options ();
-char *un_quote_string ();
-
-#ifndef S_ISLNK
-#define lstat stat
+/* 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 initialization is usually not done.  */
+#define GLOBAL
+#include "common.h"
+
+#include <argmatch.h>
+#include <closeout.h>
+#include <configmake.h>
+#include <exitfail.h>
+#include <getdate.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.  */
+
+#ifndef DEFAULT_ARCHIVE_FORMAT
+# define DEFAULT_ARCHIVE_FORMAT GNU_FORMAT
 #endif
 
-#ifndef DEFBLOCKING
-#define DEFBLOCKING 20
+#ifndef DEFAULT_ARCHIVE
+# define DEFAULT_ARCHIVE "tar.out"
 #endif
 
-#ifndef DEF_AR_FILE
-#define DEF_AR_FILE "tar.out"
+#ifndef DEFAULT_BLOCKING
+# define DEFAULT_BLOCKING 20
 #endif
 
-/* 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 nongraphic characters as pseudo short option
-   characters, starting (for no particular reason) with character 10. */
+\f
+/* Miscellaneous.  */
+
+/* Name of option using stdin.  */
+static const char *stdin_used_by;
+
+/* Doesn't return if stdin already requested.  */
+static void
+request_stdin (const char *option)
+{
+  if (stdin_used_by)
+    USAGE_ERROR ((0, 0, _("Options `-%s' and `-%s' both want standard input"),
+                 stdin_used_by, option));
+
+  stdin_used_by = option;
+}
+
+extern int rpmatch (char const *response);
 
-struct option long_options[] =
+/* Returns true if and only if the user typed an affirmative response.  */
+int
+confirm (const char *message_action, const char *message_name)
 {
-  {"create", 0, 0, 'c'},
-  {"append", 0, 0, 'r'},
-  {"extract", 0, 0, 'x'},
-  {"get", 0, 0, 'x'},
-  {"list", 0, 0, 't'},
-  {"update", 0, 0, 'u'},
-  {"catenate", 0, 0, 'A'},
-  {"concatenate", 0, 0, 'A'},
-  {"compare", 0, 0, 'd'},
-  {"diff", 0, 0, 'd'},
-  {"delete", 0, 0, 14},
-  {"help", 0, 0, 12},
-
-  {"null", 0, 0, 16},
-  {"directory", 1, 0, 'C'},
-  {"record-number", 0, &f_sayblock, 1},
-  {"files-from", 1, 0, 'T'},
-  {"label", 1, 0, 'V'},
-  {"exclude-from", 1, 0, 'X'},
-  {"exclude", 1, 0, 15},
-  {"file", 1, 0, 'f'},
-  {"block-size", 1, 0, 'b'},
-  {"version", 0, 0, 11},
-  {"verbose", 0, 0, 'v'},
-  {"totals", 0, &f_totals, 1},
-
-  {"read-full-blocks", 0, &f_reblock, 1},
-  {"starting-file", 1, 0, 'K'},
-  {"to-stdout", 0, &f_exstdout, 1},
-  {"ignore-zeros", 0, &f_ignorez, 1},
-  {"keep-old-files", 0, 0, 'k'},
-  {"same-permissions", 0, &f_use_protection, 1},
-  {"preserve-permissions", 0, &f_use_protection, 1},
-  {"modification-time", 0, &f_modified, 1},
-  {"preserve", 0, 0, 10},
-  {"same-order", 0, &f_sorted_names, 1},
-  {"same-owner", 0, &f_do_chown, 1},
-  {"preserve-order", 0, &f_sorted_names, 1},
-
-  {"newer", 1, 0, 'N'},
-  {"after-date", 1, 0, 'N'},
-  {"newer-mtime", 1, 0, 13},
-  {"incremental", 0, 0, 'G'},
-  {"listed-incremental", 1, 0, 'g'},
-  {"multi-volume", 0, &f_multivol, 1},
-  {"info-script", 1, 0, 'F'},
-  {"new-volume-script", 1, 0, 'F'},
-  {"absolute-paths", 0, &f_absolute_paths, 1},
-  {"interactive", 0, &f_confirm, 1},
-  {"confirmation", 0, &f_confirm, 1},
-
-  {"verify", 0, &f_verify, 1},
-  {"dereference", 0, &f_follow_links, 1},
-  {"one-file-system", 0, &f_local_filesys, 1},
-  {"old-archive", 0, 0, 'o'},
-  {"portability", 0, 0, 'o'},
-  {"compress", 0, 0, 'Z'},
-  {"uncompress", 0, 0, 'Z'},
-  {"compress-block", 0, &f_compress_block, 1},
-  {"gzip", 0, 0, 'z'},
-  {"ungzip", 0, 0, 'z'},
-  {"use-compress-program", 1, 0, 18},
-    
-
-  {"same-permissions", 0, &f_use_protection, 1},
-  {"sparse", 0, &f_sparse_files, 1},
-  {"tape-length", 1, 0, 'L'},
-  {"remove-files", 0, &f_remove_files, 1},
-  {"ignore-failed-read", 0, &f_ignore_failed_read, 1},
-  {"checkpoint", 0, &f_checkpoint, 1},
-  {"show-omitted-dirs", 0, &f_show_omitted_dirs, 1},
-  {"volno-file", 1, 0, 17},
-  {"force-local", 0, &f_force_local, 1},
-  {"atime-preserve", 0, &f_atime_preserve, 1},
-
-  {0, 0, 0, 0}
+  static FILE *confirm_file;
+  static int confirm_file_EOF;
+  bool status = false;
+
+  if (!confirm_file)
+    {
+      if (archive == 0 || stdin_used_by)
+       {
+         confirm_file = fopen (TTY_NAME, "r");
+         if (! confirm_file)
+           open_fatal (TTY_NAME);
+       }
+      else
+       {
+         request_stdin ("-w");
+         confirm_file = stdin;
+       }
+    }
+
+  fprintf (stdlis, "%s %s?", message_action, quote (message_name));
+  fflush (stdlis);
+
+  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);
+    }
+
+  if (confirm_file_EOF)
+    {
+      fputc ('\n', stdlis);
+      fflush (stdlis);
+    }
+
+  return status;
+}
+
+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 }
 };
 
-/*
- * Main routine for tar.
- */
-void
-main (argc, argv)
-     int argc;
-     char **argv;
+static void
+set_archive_format (char const *name)
 {
-  extern char version_string[];
+  struct fmttab const *p;
 
-  tar = argv[0];               /* JF: was "tar" Set program name */
-  filename_terminator = '\n';
-  errors = 0;
+  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;
+}
 
-  options (argc, argv);
+const char *
+archive_format_string (enum archive_format fmt)
+{
+  struct fmttab const *p;
 
-  if (!n_argv)
-    name_init (argc, argv);
+  for (p = fmttab; p->name; p++)
+    if (p->fmt == fmt)
+      return p->name;
+  return "unknown?";
+}
 
-  if (f_volno_file)
-    init_volume_number ();
+#define FORMAT_MASK(n) (1<<(n))
 
-  switch (cmd_mode)
+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")));
+}
+
+const char *
+subcommand_string (enum subcommand c)
+{
+  switch (c)
     {
-    case CMD_CAT:
-    case CMD_UPDATE:
-    case CMD_APPEND:
-      update_archive ();
-      break;
-    case CMD_DELETE:
-      junk_archive ();
-      break;
-    case CMD_CREATE:
-      create_archive ();
-      if (f_totals)
-       fprintf (stderr, "Total bytes written: %d\n", tot_written);
-      break;
-    case CMD_EXTRACT:
-      if (f_volhdr)
-       {
-         const char *err;
-         label_pattern = (struct re_pattern_buffer *)
-           ck_malloc (sizeof *label_pattern);
-         err = re_compile_pattern (f_volhdr, strlen (f_volhdr),
-                                   label_pattern);
-         if (err)
-           {
-             fprintf (stderr, "Bad regular expression: %s\n",
-                      err);
-             errors++;
-             break;
-           }
+    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.  */
+
+enum
+{
+  ANCHORED_OPTION = CHAR_MAX + 1,
+  ATIME_PRESERVE_OPTION,
+  BACKUP_OPTION,
+  CHECK_DEVICE_OPTION,
+  CHECKPOINT_OPTION,
+  CHECKPOINT_ACTION_OPTION,
+  DELAY_DIRECTORY_RESTORE_OPTION,
+  HARD_DEREFERENCE_OPTION,
+  DELETE_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,
+  IGNORE_CASE_OPTION,
+  IGNORE_COMMAND_ERROR_OPTION,
+  IGNORE_FAILED_READ_OPTION,
+  INDEX_FILE_OPTION,
+  KEEP_NEWER_FILES_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_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,
+  TEST_LABEL_OPTION,
+  TOTALS_OPTION,
+  TO_COMMAND_OPTION,
+  TRANSFORM_OPTION,
+  UNQUOTE_OPTION,
+  UTC_OPTION,
+  VOLNO_FILE_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 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")
+"\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\
+  nil, existing   numbered if numbered backups exist, simple otherwise\n\
+  never, simple   always make simple backups\n");
+
+
+/* NOTE:
+
+   Available option letters are DEQY and eqy. Consider the following
+   assignments:
+
+   [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?]
+
+   y  per-file 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:"), GRID },
+
+  {"list", 't', 0, 0,
+   N_("list the contents of an archive"), GRID+1 },
+  {"extract", 'x', 0, 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"), GRID+1 },
+  {"diff", 'd', 0, 0,
+   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"), GRID+1 },
+  {"update", 'u', 0, 0,
+   N_("only append files newer than copy in archive"), GRID+1 },
+  {"catenate", 'A', 0, 0,
+   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!)"), 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:"), GRID },
+
+  {"sparse", 'S', 0, 0,
+   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"), GRID+1 },
+  {"listed-incremental", 'g', N_("FILE"), 0,
+   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"), 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 the -T option;"
+      " NUMBER defaults to 1"), GRID+1 },
+  {"seek", 'n', NULL, 0,
+   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:"), GRID },
+
+  {"verify", 'W', 0, 0,
+   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"), GRID+1 },
+  {"keep-old-files", 'k', 0, 0,
+   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"), GRID+1 },
+  {"overwrite", OVERWRITE_OPTION, 0, 0,
+   N_("overwrite existing files when extracting"), GRID+1 },
+  {"unlink-first", 'U', 0, 0,
+   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"), GRID+1 },
+  {"no-overwrite-dir", NO_OVERWRITE_DIR_OPTION, 0, 0,
+   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:"), GRID },
+
+  {"to-stdout", 'O', 0, 0,
+   N_("extract files to standard output"), GRID+1 },
+  {"to-command", TO_COMMAND_OPTION, N_("COMMAND"), 0,
+   N_("pipe extracted files to another program"), GRID+1 },
+  {"ignore-command-error", IGNORE_COMMAND_ERROR_OPTION, 0, 0,
+   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"), GRID+1 },
+#undef GRID
+
+#define GRID 50
+  {NULL, 0, NULL, 0,
+   N_("Handling of file attributes:"), GRID },
+
+  {"owner", OWNER_OPTION, N_("NAME"), 0,
+   N_("force NAME as owner for added files"), GRID+1 },
+  {"group", GROUP_OPTION, N_("NAME"), 0,
+   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"), 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"), GRID+1 },
+  {"same-owner", SAME_OWNER_OPTION, 0, 0,
+   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 (default for ordinary users)"), GRID+1 },
+  {"numeric-owner", NUMERIC_OWNER_OPTION, 0, 0,
+   N_("always use numbers for user/group names"), GRID+1 },
+  {"preserve-permissions", 'p', 0, 0,
+   N_("extract information about file permissions (default for superuser)"),
+   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)"), GRID+1 },
+  {"preserve-order", 's', 0, 0,
+   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"), 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:"), GRID },
+
+  {"file", 'f', N_("ARCHIVE"), 0,
+   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"), GRID+1 },
+  {"rmt-command", RMT_COMMAND_OPTION, N_("COMMAND"), 0,
+   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"), 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"), GRID+1 },
+#endif
+  {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"), GRID+1 },
+  {"tape-length", 'L', N_("NUMBER"), 0,
+   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)"), 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"), GRID+1 },
+#undef GRID
+
+#define GRID 70
+  {NULL, 0, NULL, 0,
+   N_("Device blocking:"), GRID },
+
+  {"blocking-factor", 'b', N_("BLOCKS"), 0,
+   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"), GRID+1 },
+  {"ignore-zeros", 'i', 0, 0,
+   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)"), GRID+1 },
+#undef GRID
+
+#define GRID 80
+  {NULL, 0, NULL, 0,
+   N_("Archive format selection:"), GRID },
+
+  {"format", 'H', N_("FORMAT"), 0,
+   N_("create archive of the given format"), GRID+1 },
+
+  {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"), GRID+3 },
+  {"  gnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+   N_("GNU tar 1.13.x format"), GRID+3 },
+  {"  ustar", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+   N_("POSIX 1003.1-1988 (ustar) format"), GRID+3 },
+  {"  pax", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+   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"), GRID+8 },
+  {"portability", 0, 0, OPTION_ALIAS, NULL, GRID+8 },
+  {"posix", POSIX_OPTION, 0, 0,
+   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"), 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:"), GRID },
+
+  {"add-file", ARGP_KEY_ARG, N_("FILE"), 0,
+   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"), GRID+1 },
+  {"files-from", 'T', N_("FILE"), 0,
+   N_("get names to extract or create from FILE"), GRID+1 },
+  {"null", NULL_OPTION, 0, 0,
+   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)"), GRID+1 },
+  {"no-unquote", NO_UNQUOTE_OPTION, 0, 0,
+   N_("do not unquote filenames read with -T"), GRID+1 },
+  {"exclude", EXCLUDE_OPTION, N_("PATTERN"), 0,
+   N_("exclude files, given as a PATTERN"), GRID+1 },
+  {"exclude-from", 'X', N_("FILE"), 0,
+   N_("exclude patterns listed in FILE"), GRID+1 },
+  {"exclude-caches", EXCLUDE_CACHES_OPTION, 0, 0,
+   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"), GRID+1 },
+  {"one-file-system", ONE_FILE_SYSTEM_OPTION, 0, 0,
+   N_("stay in local file system when creating archive"), GRID+1 },
+  {"recursion", RECURSION_OPTION, 0, 0,
+   N_("recurse into directories (default)"), GRID+1 },
+  {"absolute-names", 'P', 0, 0,
+   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"), 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"), GRID+1 },
+  {"newer", 'N', N_("DATE-OR-FILE"), 0,
+   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"), GRID+1 },
+  {"backup", BACKUP_OPTION, N_("CONTROL"), OPTION_ARG_OPTIONAL,
+   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)"), 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_("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_("wildcards match `/' (default for exclusion)"), GRID+1 },
+#undef GRID
+
+#define GRID 130
+  {NULL, 0, NULL, 0,
+   N_("Informative output:"), GRID },
+
+  {"verbose", 'v', 0, 0,
+   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 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"), GRID+1 },
+  {"block-number", 'R', 0, 0,
+   N_("show block number within archive with each message"), GRID+1 },
+  {"interactive", 'w', 0, 0,
+   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"), 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"), 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:"), GRID },
+
+  {NULL, 'o', 0, 0,
+   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:"), GRID },
+
+  {"restrict", RESTRICT_OPTION, 0, 0,
+   N_("disable use of some potentially harmful options"), -1 },
+#undef GRID
+
+  {0, 0, 0, 0, 0, 0}
+};
+
+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
+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)
+{
+  return xasprintf (
+           "--format=%s -f%s -b%d --quoting-style=%s --rmt-command=%s"
+#ifdef REMOTE_SHELL
+           " --rsh-command=%s"
+#endif
+           ,
+           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' or `--test-label' option")));
+
+  subcommand_option = subcommand;
+}
+
+static void
+set_use_compress_program_option (const char *string)
+{
+  if (use_compress_program_option
+      && strcmp (use_compress_program_option, string) != 0)
+    USAGE_ERROR ((0, 0, _("Conflicting compression options")));
+
+  use_compress_program_option = string;
+}
+\f
+static RETSIGTYPE
+sigstat (int signo)
+{
+  compute_duration ();
+  print_total_stats ();
+#ifndef HAVE_SIGACTION
+  signal (signo, sigstat);
+#endif
+}
+
+static void
+stat_on_signal (int signo)
+{
+#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 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")));
        }
-      extr_init ();
-      read_and (extract_archive);
-      break;
-    case CMD_LIST:
-      if (f_volhdr)
+      *ts = get_stat_mtime (&st);
+    }
+  else
+    {
+      if (! get_date (ts, str, NULL))
        {
-         const char *err;
-         label_pattern = (struct re_pattern_buffer *)
-           ck_malloc (sizeof *label_pattern);
-         err = re_compile_pattern (f_volhdr, strlen (f_volhdr),
-                                   label_pattern);
-         if (err)
-           {
-             fprintf (stderr, "Bad regular expression: %s\n",
-                      err);
-             errors++;
-             break;
-           }
+         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;
        }
-      read_and (list_archive);
-#if 0
-      if (!errors)
-       errors = different;
-#endif
-      break;
-    case CMD_DIFF:
-      diff_init ();
-      read_and (diff_archive);
-      break;
-    case CMD_VERSION:
-      fprintf (stderr, "%s\n", version_string);
-      break;
-    case CMD_NONE:
-      msg ("you must specify exactly one of the r, c, t, x, or d options\n");
-      fprintf (stderr, "For more information, type ``%s --help''.\n", tar);
-      exit (EX_ARGSBAD);
     }
-  if (f_volno_file)
-    closeout_volume_number ();
-  exit (errors);
-  /* NOTREACHED */
+  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;
 
-/*
- * Parse the options for tar.
+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_skip         /* Empty (zero-length) entry encountered, skip it */
+  };
+
+/* Read from FP a sequence of characters up to TERM and put them
+   into STK.
  */
-void
-options (argc, argv)
-     int argc;
-     char **argv;
+static enum read_file_list_state
+read_name_from_file (FILE *fp, struct obstack *stk, int term)
 {
-  register int c;              /* Option letter */
-  int ind = -1;
-
-  /* Set default option values */
-  blocking = DEFBLOCKING;      /* From Makefile */
-  ar_files = (char **) ck_malloc (sizeof (char *) * 10);
-  ar_files_len = 10;
-  n_ar_files = 0;
-  cur_ar_file = 0;
-
-  /* Parse options */
-  while ((c = getoldopt (argc, argv,
-              "-01234567Ab:BcC:df:F:g:GhikK:lL:mMN:oOpPrRsStT:uvV:wWxX:zZ",
-                        long_options, &ind)) != EOF)
+  int c;
+  size_t counter = 0;
+
+  for (c = getc (fp); c != EOF && c != term; c = getc (fp))
     {
-      switch (c)
+      if (c == 0)
        {
-       case 0:         /* long options that set a single flag */
-         break;
-       case 1:
-         /* File name or non-parsed option */
-         name_add (optarg);
-         break;
-       case 'C':
-         name_add ("-C");
-         name_add (optarg);
-         break;
-       case 10:                /* preserve */
-         f_use_protection = f_sorted_names = 1;
-         break;
-       case 11:
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_VERSION;
-         break;
-       case 12:                /* help */
-         printf ("This is GNU tar, the tape archiving program.\n");
-         describe ();
-         exit (1);
-       case 13:
-         f_new_files++;
-         goto get_newer;
-
-       case 14:                /* Delete in the archive */
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_DELETE;
-         break;
+         /* We have read a zero separator. The file possibly is
+            zero-separated */
+         return file_list_zero;
+       }
+      obstack_1grow (stk, c);
+      counter++;
+    }
 
-       case 15:
-         f_exclude++;
-         add_exclude (optarg);
-         break;
+  if (counter == 0 && c != EOF)
+    return file_list_skip;
 
-       case 16:                /* -T reads null terminated filenames. */
-         filename_terminator = '\0';
-         break;
+  obstack_1grow (stk, 0);
 
-       case 17:
-         f_volno_file = optarg;
-         break;
+  return (counter == 0 && c == EOF) ? file_list_end : file_list_success;
+}
 
-       case 18:
-         if (f_compressprog)
-           {
-             msg ("Only one compression option permitted\n");
-             exit (EX_ARGSBAD);
-           }
-         f_compressprog = optarg;
-         break;
+\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 */
 
-       case 'g':               /* We are making a GNU dump; save
-                                  directories at the beginning of
-                                  the archive, and include in each
-                                  directory its contents */
-         if (f_oldarch)
-           goto badopt;
-         f_gnudump++;
-         gnu_dumpfile = optarg;
-         break;
+/* 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;
 
-       case '0':
-       case '1':
-       case '2':
-       case '3':
-       case '4':
-       case '5':
-       case '6':
-       case '7':
-         {
-           /* JF this'll have to be modified for other
-                                  systems, of course! */
-           int d, add;
-           static char buf[50];
-
-           d = getoldopt (argc, argv, "lmh");
-#ifdef MAYBEDEF
-           sprintf (buf, "/dev/rmt/%d%c", c, d);
-#else
-#ifndef LOW_NUM
-#define LOW_NUM 0
-#define MID_NUM 8
-#define HGH_NUM 16
+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;
+}
+
+/* Default density numbers for [0-9][lmh] device specifications */
+
+#ifndef LOW_DENSITY_NUM
+# define LOW_DENSITY_NUM 0
 #endif
-           if (d == 'l')
-             add = LOW_NUM;
-           else if (d == 'm')
-             add = MID_NUM;
-           else if (d == 'h')
-             add = HGH_NUM;
-           else
-             goto badopt;
-
-           sprintf (buf, "/dev/rmt%d", add + c - '0');
+
+#ifndef MID_DENSITY_NUM
+# define MID_DENSITY_NUM 8
 #endif
-           if (n_ar_files == ar_files_len)
-             ar_files
-               = (char **)
-               ck_malloc (sizeof (char *)
-                          * (ar_files_len *= 2));
-           ar_files[n_ar_files++] = buf;
-         }
-         break;
 
-       case 'A':               /* Arguments are tar files,
-                                  just cat them onto the end
-                                  of the archive.  */
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_CAT;
-         break;
+#ifndef HIGH_DENSITY_NUM
+# define HIGH_DENSITY_NUM 16
+#endif
 
-       case 'b':               /* Set blocking factor */
-         blocking = intconv (optarg);
-         break;
+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;
+  int term = filename_terminator;
+
+  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);
+    }
 
-       case 'B':               /* Try to reblock input */
-         f_reblock++;          /* For reading 4.2BSD pipes */
+  while ((read_state = read_name_from_file (fp, &argv_stk, term))
+        != file_list_end)
+    {
+      switch (read_state)
+       {
+       case file_list_success:
+         count++;
          break;
 
-       case 'c':               /* Create an archive */
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_CREATE;
+       case file_list_end: /* won't happen, just to pacify gcc */
          break;
 
-#if 0
-       case 'C':
-         if (chdir (optarg) < 0)
-           msg_perror ("Can't change directory to %d", optarg);
-         break;
-#endif
+       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 'd':               /* Find difference tape/disk */
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_DIFF;
+       case file_list_skip:
          break;
+       }
+    }
 
-       case 'f':               /* Use ar_file for the archive */
-         if (n_ar_files == ar_files_len)
-           ar_files
-             = (char **) ck_malloc (sizeof (char *)
-                                    * (ar_files_len *= 2));
+  if (!is_stdin)
+    fclose (fp);
 
-         ar_files[n_ar_files++] = optarg;
-         break;
+  if (count == 0)
+    return;
 
-       case 'F':
-         /* Since -F is only useful with -M , make it implied */
-         f_run_script_at_end++;/* run this script at the end */
-         info_script = optarg; /* of each tape */
-         f_multivol++;
-         break;
+  start = obstack_finish (&argv_stk);
 
-       case 'G':               /* We are making a GNU dump; save
-                                  directories at the beginning of
-                                  the archive, and include in each
-                                  directory its contents */
-         if (f_oldarch)
-           goto badopt;
-         f_gnudump++;
-         gnu_dumpfile = 0;
-         break;
+  if (term == 0)
+    for (p = start; *p; p += strlen (p) + 1)
+      if (p[0] == '-')
+       count++;
 
-       case 'h':
-         f_follow_links++;     /* follow symbolic links */
-         break;
+  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]));
 
-       case 'i':
-         f_ignorez++;          /* Ignore zero records (eofs) */
-         /*
-                        * This can't be the default, because Unix tar
-                        * writes two records of zeros, then pads out the
-                        * block with garbage.
-                        */
-         break;
+  state->argc = new_argc;
 
-       case 'k':               /* Don't overwrite files */
-#ifdef NO_OPEN3
-         msg ("can't keep old files on this system");
-         exit (EX_ARGSBAD);
-#else
-         f_keep++;
-#endif
-         break;
+  for (i = state->next, p = start; *p; p += strlen (p) + 1, i++)
+    {
+      if (term == 0 && p[0] == '-')
+       state->argv[i++] = "--add-file";
+      state->argv[i] = p;
+    }
+}
 
-       case 'K':
-         f_startfile++;
-         addname (optarg);
-         break;
+\f
+static char *
+tar_help_filter (int key, const char *text, void *input)
+{
+  struct obstack stk;
+  char *s;
 
-       case 'l':               /* When dumping directories, don't
-                                  dump files/subdirectories that are
-                                  on other filesystems. */
-         f_local_filesys++;
-         break;
+  switch (key)
+    {
+    default:
+      s = (char*) text;
+      break;
 
-       case 'L':
-         tape_length = intconv (optarg);
-         f_multivol++;
-         break;
-       case 'm':
-         f_modified++;
-         break;
+    case 'j':
+      s = xasprintf (_("filter the archive through %s"), BZIP2_PROGRAM);
+      break;
 
-       case 'M':               /* Make Multivolume archive:
-                                  When we can't write any more
-                                  into the archive, re-open it,
-                                  and continue writing */
-         f_multivol++;
-         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;
 
-       case 'N':               /* Only write files newer than X */
-       get_newer:
-         f_new_files++;
-         new_time = get_date (optarg, (PTR) 0);
-         if (new_time == (time_t) - 1)
+  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] == '}')
            {
-             msg ("invalid date format `%s'", optarg);
-             exit (EX_ARGSBAD);
+             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);
            }
-         break;
+         else
+           obstack_grow (&stk, p, len);
+       }
+      else
+       obstack_grow (&stk, arg, seglen);
 
-       case 'o':               /* Generate old archive */
-         if (f_gnudump /* || f_dironly */ )
-           goto badopt;
-         f_oldarch++;
-         break;
+      arg += seglen;
+      if (*arg)
+       {
+         obstack_1grow (&stk, *arg);
+         arg++;
+       }
+    }
+  obstack_1grow (&stk, 0);
+  res = xstrdup (obstack_finish (&stk));
+  obstack_free (&stk, NULL);
+  return res;
+}
 
-       case 'O':
-         f_exstdout++;
-         break;
+\f
+#define TAR_SIZE_SUFFIXES "bBcGgkKMmPTtw"
 
-       case 'p':
-         f_use_protection++;
-         break;
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  struct tar_args *args = state->input;
 
-       case 'P':
-         f_absolute_paths++;
-         break;
+  switch (key)
+    {
+    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;
+       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_dir (arg);
+      break;
+
+    case 'd':
+      set_subcommand_option (DIFF_SUBCOMMAND);
+      break;
+
+    case 'f':
+      if (archive_names == 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;
+
+    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 FULL_TIME_OPTION:
+      full_time_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 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
+        the record with garbage.  */
+
+      ignore_zeros_option = true;
+      break;
+
+    case 'j':
+      set_use_compress_program_option (BZIP2_PROGRAM);
+      break;
+
+    case 'J':
+      set_use_compress_program_option (XZ_PROGRAM);
+      break;
+
+    case 'k':
+      /* Don't replace existing files.  */
+      old_files_option = KEEP_OLD_FILES;
+      break;
+
+    case 'K':
+      starting_file_option = true;
+      addname (arg, 0, true, NULL);
+      break;
+
+    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;
+       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")));
+       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;
+
+    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 MTIME_OPTION:
+      get_date_or_file (args, "--mtime", arg, &mtime_option);
+      set_mtime_option = true;
+      break;
+
+    case 'n':
+      seek_option = 1;
+      break;
+
+    case NO_SEEK_OPTION:
+      seek_option = 0;
+      break;
+
+    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")));
+      get_date_or_file (args,
+                       key == NEWER_MTIME_OPTION ? "--newer-mtime"
+                       : "--after-date", arg, &newer_mtime_option);
+      break;
+
+    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 and most
+        BSD-derived systems.  This is a consequence of the block/record
+        terminology confusion.  */
+
+      block_number_option = true;
+      break;
+
+    case 's':
+      /* Names to extract are sorted.  */
+
+      same_order_option = true;
+      break;
+
+    case 'S':
+      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
+        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++;
+      warning_option |= WARN_VERBOSE_WARNINGS;
+      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,
+                           MAKE_EXCL_OPTIONS (args), '\n')
+         != 0)
+       {
+         int e = errno;
+         FATAL_ERROR ((0, e, "%s", quotearg_colon (arg)));
+       }
+      break;
+
+    case 'z':
+      set_use_compress_program_option (GZIP_PROGRAM);
+      break;
+
+    case 'Z':
+      set_use_compress_program_option (COMPRESS_PROGRAM);
+      break;
+
+    case ANCHORED_OPTION:
+      args->matching_flags |= EXCLUDE_ANCHORED;
+      break;
+
+    case ATIME_PRESERVE_OPTION:
+      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:
+      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:
+      backup_option = true;
+      if (arg)
+       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, MAKE_EXCL_OPTIONS (args));
+      break;
+
+    case EXCLUDE_CACHES_OPTION:
+      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:
+      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->matching_flags |= 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),
+                         _("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->include_anchored = 0; /* Clear the default for comman line args */
+      args->matching_flags &= ~ EXCLUDE_ANCHORED;
+      break;
+
+    case NO_IGNORE_CASE_OPTION:
+      args->matching_flags &= ~ 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_QUOTE_CHARS_OPTION:
+      for (;*arg; arg++)
+       set_char_quoting (NULL, *arg, 0);
+      break;
+
+    case NO_WILDCARDS_OPTION:
+      args->wildcards = disable_wildcards;
+      break;
+
+    case NO_WILDCARDS_MATCH_SLASH_OPTION:
+      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;
+
+    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_DIR_OPTION:
+      old_files_option = DEFAULT_OLD_FILES;
+      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 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:
+      {
+       char *tmp = expand_pax_option (args, arg);
+       args->pax_option = true;
+       xheader_set_option (tmp);
+       free (tmp);
+      }
+      break;
+
+    case POSIX_OPTION:
+      set_archive_format ("posix");
+      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, NULL, 10, &u, TAR_SIZE_SUFFIXES) == 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 RESTRICT_OPTION:
+      restrict_option = true;
+      break;
+
+    case RMT_COMMAND_OPTION:
+      rmt_command = arg;
+      break;
+
+    case RSH_COMMAND_OPTION:
+      rsh_command_option = arg;
+      break;
+
+    case SHOW_DEFAULTS_OPTION:
+      {
+       char *s = format_default_settings ();
+       printf ("%s\n", s);
+       close_stdout ();
+       free (s);
+       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 SHOW_TRANSFORMED_NAMES_OPTION:
+      show_transformed_names_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:
+      if (arg)
+       set_stat_signal (arg);
+      else
+       totals_option = true;
+      break;
+
+    case TRANSFORM_OPTION:
+      set_transform_expr (arg);
+      break;
+
+    case 'I':
+      set_use_compress_program_option (arg);
+      break;
+
+    case VOLNO_FILE_OPTION:
+      volno_file_option = arg;
+      break;
+
+    case WILDCARDS_OPTION:
+      args->wildcards = enable_wildcards;
+      break;
+
+    case WILDCARDS_MATCH_SLASH_OPTION:
+      args->matching_flags &= ~ FNM_FILE_NAME;
+      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 WARNING_OPTION:
+      set_warning_option (arg);
+      break;
 
-       case 'r':               /* Append files to the archive */
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_APPEND;
-         break;
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
 
-       case 'R':
-         f_sayblock++;         /* Print block #s for debug */
-         break;                /* of bad tar archives */
+#ifdef DEVICE_PREFIX
+      {
+       int device = key - '0';
+       int density;
+       static char buf[sizeof DEVICE_PREFIX + 10];
+       char *cursor;
 
-       case 's':
-         f_sorted_names++;     /* Names to extr are sorted */
-         break;
+       if (arg[1])
+         argp_error (state, _("Malformed density argument: %s"), quote (arg));
 
-       case 'S':               /* deal with sparse files */
-         f_sparse_files++;
-         break;
-       case 't':
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_LIST;
-         f_verbose++;          /* "t" output == "cv" or "xv" */
-         break;
+       strcpy (buf, DEVICE_PREFIX);
+       cursor = buf + strlen (buf);
 
-       case 'T':
-         name_file = optarg;
-         f_namefile++;
-         break;
+#ifdef DENSITY_LETTER
 
-       case 'u':               /* Append files to the archive that
-                                  aren't there, or are newer than the
-                                  copy in the archive */
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_UPDATE;
-         break;
+       sprintf (cursor, "%d%c", device, arg[0]);
 
-       case 'v':
-         f_verbose++;
-         break;
+#else /* not DENSITY_LETTER */
 
-       case 'V':
-         f_volhdr = optarg;
-         break;
+       switch (arg[0])
+         {
+         case 'l':
+           device += LOW_DENSITY_NUM;
+           break;
 
-       case 'w':
-         f_confirm++;
-         break;
+         case 'm':
+           device += MID_DENSITY_NUM;
+           break;
 
-       case 'W':
-         f_verify++;
-         break;
+         case 'h':
+           device += HIGH_DENSITY_NUM;
+           break;
 
-       case 'x':               /* Extract files from the archive */
-         if (cmd_mode != CMD_NONE)
-           goto badopt;
-         cmd_mode = CMD_EXTRACT;
-         break;
+         default:
+           argp_error (state, _("Unknown density: `%c'"), arg[0]);
+         }
+       sprintf (cursor, "%d", device);
 
-       case 'X':
-         f_exclude++;
-         add_exclude_file (optarg);
-         break;
+#endif /* not DENSITY_LETTER */
 
-       case 'z':
-         if (f_compressprog)
-           {
-             msg ("Only one compression option permitted\n");
-             exit (EX_ARGSBAD);
-           }
-         f_compressprog = "gzip";
-         break;
+       if (archive_names == 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;
 
-       case 'Z':
-         if (f_compressprog)
-           {
-             msg ("Only one compression option permitted\n");
-             exit (EX_ARGSBAD);
-           }
-         f_compressprog = "compress";
-         break;
+#else /* not DEVICE_PREFIX */
 
-       case '?':
-       badopt:
-         msg ("Unknown option.  Use '%s --help' for a complete list of options.", tar);
-         exit (EX_ARGSBAD);
+      argp_error (state,
+                 _("Options `-[0-7][lmh]' not supported by *this* tar"));
 
-       }
-    }
+#endif /* not DEVICE_PREFIX */
 
-  blocksize = blocking * RECORDSIZE;
-  if (n_ar_files == 0)
-    {
-      n_ar_files = 1;
-      ar_files[0] = getenv ("TAPE");   /* From environment, or */
-      if (ar_files[0] == 0)
-       ar_files[0] = DEF_AR_FILE;      /* From Makefile */
-    }
-  if (n_ar_files > 1 && !f_multivol)
-    {
-      msg ("Multiple archive files requires --multi-volume\n");
-      exit (EX_ARGSBAD);
-    }
-  if (f_compress_block && !f_compressprog)
-    {
-      msg ("You must use a compression option (--gzip, --compress\n\
-or --use-compress-program) with --f_compress_block.\n");
-      exit (EX_ARGSBAD);
+    default:
+      return ARGP_ERR_UNKNOWN;
     }
+  return 0;
 }
 
+static struct argp argp = {
+  options,
+  parse_opt,
+  N_("[FILE]..."),
+  doc,
+  NULL,
+  tar_help_filter,
+  NULL
+};
 
-/*
- * Print as much help as the user's gonna get.
- *
- * We have to sprinkle in the KLUDGE lines because too many compilers
- * cannot handle character strings longer than about 512 bytes.  Yuk!
- * In particular, MS-DOS and Xenix MSC and PDP-11 V7 Unix have this
- * problem.
- */
 void
-describe ()
+usage (int status)
 {
-  puts ("choose one of the following:");
-  fputs ("\
--A, --catenate,\n\
-    --concatenate      append tar files to an archive\n\
--c, --create           create a new archive\n\
--d, --diff,\n\
-    --compare          find differences between archive and file system\n\
---delete               delete from the archive (not for use on mag tapes!)\n\
--r, --append           append files to the end of an archive\n\
--t, --list             list the contents of an archive\n\
--u, --update           only append files that are newer than copy in archive\n\
--x, --extract,\n\
-    --get              extract files from an archive\n", stdout);
-
-  fprintf (stdout, "\
-Other options:\n\
---atime-preserve       don't change access times on dumped files\n\
--b, --block-size N     block size of Nx512 bytes (default N=%d)\n", DEFBLOCKING);
-  fputs ("\
--B, --read-full-blocks reblock as we read (for reading 4.2BSD pipes)\n\
--C, --directory DIR    change to directory DIR\n\
---checkpoint           print directory names while reading the archive\n\
-", stdout);                    /* KLUDGE */
-  fprintf (stdout, "\
--f, --file [HOSTNAME:]F        use archive file or device F (default %s)\n",
-          DEF_AR_FILE);
-  fputs ("\
---force-local          archive file is local even if has a colon\n\
--F, --info-script F\n\
-    --new-volume-script F run script at end of each tape (implies -M)\n\
--G, --incremental      create/list/extract old GNU-format incremental backup\n\
--g, --listed-incremental F create/list/extract new GNU-format incremental backup\n\
--h, --dereference      don't dump symlinks; dump the files they point to\n\
--i, --ignore-zeros     ignore blocks of zeros in archive (normally mean EOF)\n\
---ignore-failed-read   don't exit with non-zero status on unreadable files\n\
--k, --keep-old-files   keep existing files; don't overwrite them from archive\n\
--K, --starting-file F  begin at file F in the archive\n\
--l, --one-file-system  stay in local file system when creating an archive\n\
--L, --tape-length N    change tapes after writing N*1024 bytes\n\
-", stdout);                    /* KLUDGE */
-  fputs ("\
--m, --modification-time        don't extract file modified time\n\
--M, --multi-volume     create/list/extract multi-volume archive\n\
--N, --after-date DATE,\n\
-    --newer DATE       only store files newer than DATE\n\
--o, --old-archive,\n\
-    --portability      write a V7 format archive, rather than ANSI format\n\
--O, --to-stdout                extract files to standard output\n\
--p, --same-permissions,\n\
-    --preserve-permissions extract all protection information\n\
--P, --absolute-paths   don't strip leading `/'s from file names\n\
---preserve             like -p -s\n\
-", stdout);                    /* KLUDGE */
-  fputs ("\
--R, --record-number    show record number within archive with each message\n\
---remove-files         remove files after adding them to the archive\n\
--s, --same-order,\n\
-    --preserve-order   list of names to extract is sorted to match archive\n\
---same-owner           create extracted files with the same ownership \n\
--S, --sparse           handle sparse files efficiently\n\
--T, --files-from F     get names to extract or create from file F\n\
---null                 -T reads null-terminated names, disable -C\n\
---totals               print total bytes written with --create\n\
--v, --verbose          verbosely list files processed\n\
--V, --label NAME       create archive with volume name NAME\n\
---version              print tar program version number\n\
--w, --interactive,\n\
-    --confirmation     ask for confirmation for every action\n\
-", stdout);                    /* KLUDGE */
-  fputs ("\
--W, --verify           attempt to verify the archive after writing it\n\
---exclude FILE         exclude file FILE\n\
--X, --exclude-from FILE        exclude files listed in FILE\n\
--Z, --compress,\n\
-    --uncompress       filter the archive through compress\n\
--z, --gzip,\n\
-    --ungzip           filter the archive through gzip\n\
---use-compress-program PROG\n\
-                       filter the archive through PROG (which must accept -d)\n\
--[0-7][lmh]            specify drive and density\n\
-", stdout);
+  argp_help (&argp, stderr, ARGP_HELP_SEE, (char*) program_name);
+  close_stdout ();
+  exit (status);
 }
 
-void
-name_add (name)
-     char *name;
+/* Parse the options for tar.  */
+
+static struct argp_option *
+find_argp_option (struct argp_option *o, int letter)
 {
-  if (n_indalloc == n_indused)
-    {
-      n_indalloc += 10;
-      n_ind = (char **) (n_indused ? ck_realloc (n_ind, n_indalloc * sizeof (char *)): ck_malloc (n_indalloc * sizeof (char *)));
-    }
-  n_ind[n_indused++] = name;
+  for (;
+       !(o->name == NULL
+        && o->key == 0
+        && o->arg == 0
+        && o->flags == 0
+        && o->doc == NULL); o++)
+    if (o->key == letter)
+      return o;
+  return NULL;
 }
 
-/*
- * Set up to gather file names for tar.
- *
- * They can either come from stdin or from argv.
- */
-void
-name_init (argc, argv)
-     int argc;
-     char **argv;
-{
+static const char *tar_authors[] = {
+  "John Gilmore",
+  "Jay Fenlason",
+  NULL
+};
 
-  if (f_namefile)
-    {
-      if (optind < argc)
-       {
-         msg ("too many args with -T option");
-         exit (EX_ARGSBAD);
-       }
-      if (!strcmp (name_file, "-"))
-       {
-         namef = stdin;
-       }
-      else
-       {
-         namef = fopen (name_file, "r");
-         if (namef == NULL)
-           {
-             msg_perror ("can't open file %s", name_file);
-             exit (EX_BADFILE);
-           }
-       }
-    }
-  else
+static void
+decode_options (int argc, char **argv)
+{
+  int idx;
+  struct tar_args args;
+
+  argp_version_setup ("tar", tar_authors);
+
+  /* Set some default option values.  */
+  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 = false;
+  args.compress_autodetect = false;
+
+  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;
+  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.  */
+
+  if (argc > 1 && argv[1][0] != '-')
     {
-      /* Get file names from argv, after options. */
-      n_argc = argc;
-      n_argv = argv;
-    }
-}
+      int new_argc;            /* argc value for rearranged arguments */
+      char **new_argv;         /* argv value for rearranged arguments */
+      char *const *in;         /* cursor into original argv */
+      char **out;              /* cursor into rearranged argv */
+      const char *letter;      /* cursor into old option letters */
+      char buffer[3];          /* constructed option buffer */
 
-/* Read the next filename read from STREAM and null-terminate it.
-   Put it into BUFFER, reallocating and adjusting *PBUFFER_SIZE if necessary.
-   Return the new value for BUFFER, or NULL at end of file. */
+      /* Initialize a constructed option.  */
 
-char *
-read_name_from_file (buffer, pbuffer_size, stream)
-     char *buffer;
-     size_t *pbuffer_size;
-     FILE *stream;
-{
-  register int c;
-  register int indx = 0;
-  register size_t buffer_size = *pbuffer_size;
+      buffer[0] = '-';
+      buffer[2] = '\0';
 
-  while ((c = getc (stream)) != EOF && c != filename_terminator)
-    {
-      if (indx == buffer_size)
-       {
-         buffer_size += NAMSIZ;
-         buffer = ck_realloc (buffer, buffer_size + 2);
-       }
-      buffer[indx++] = c;
-    }
-  if (indx == 0 && c == EOF)
-    return NULL;
-  if (indx == buffer_size)
-    {
-      buffer_size += NAMSIZ;
-      buffer = ck_realloc (buffer, buffer_size + 2);
-    }
-  buffer[indx] = '\0';
-  *pbuffer_size = buffer_size;
-  return buffer;
-}
+      /* Allocate a new argument array, and copy program name in it.  */
 
-/*
- * Get the next name from argv or the name file.
- *
- * Result is in static storage and can't be relied upon across two calls.
- *
- * If CHANGE_DIRS is non-zero, treat a filename of the form "-C" as
- * meaning that the next filename is the name of a directory to change to.
- * If `filename_terminator' is '\0', CHANGE_DIRS is effectively always 0.
- */
+      new_argc = argc - 1 + strlen (argv[1]);
+      new_argv = xmalloc ((new_argc + 1) * sizeof (char *));
+      in = argv;
+      out = new_argv;
+      *out++ = *in++;
 
-char *
-name_next (change_dirs)
-     int change_dirs;
-{
-  static char *buffer;         /* Holding pattern */
-  static int buffer_siz;
-  register char *p;
-  register char *q = 0;
-  register int next_name_is_dir = 0;
-  extern char *un_quote_string ();
-
-  if (buffer_siz == 0)
-    {
-      buffer = ck_malloc (NAMSIZ + 2);
-      buffer_siz = NAMSIZ;
-    }
-  if (filename_terminator == '\0')
-    change_dirs = 0;
-tryagain:
-  if (namef == NULL)
-    {
-      if (n_indscan < n_indused)
-       p = n_ind[n_indscan++];
-      else if (optind < n_argc)
-       /* Names come from argv, after options */
-       p = n_argv[optind++];
-      else
-       {
-         if (q)
-           msg ("Missing filename after -C");
-         return NULL;
-       }
+      /* Copy each old letter option as a separate option, and have the
+        corresponding argument moved next to it.  */
 
-      /* JF trivial support for -C option.  I don't know if
-                  chdir'ing at this point is dangerous or not.
-                  It seems to work, which is all I ask. */
-      if (change_dirs && !q && p[0] == '-' && p[1] == 'C' && p[2] == '\0')
-       {
-         q = p;
-         goto tryagain;
-       }
-      if (q)
+      for (letter = *in++; *letter; letter++)
        {
-         if (chdir (p) < 0)
-           msg_perror ("Can't chdir to %s", p);
-         q = 0;
-         goto tryagain;
-       }
-      /* End of JF quick -C hack */
+         struct argp_option *opt;
 
-#if 0
-      if (f_exclude && check_exclude (p))
-       goto tryagain;
-#endif
-      return un_quote_string (p);
-    }
-  while (p = read_name_from_file (buffer, &buffer_siz, namef))
-    {
-      buffer = p;
-      if (*p == '\0')
-       continue;               /* Ignore empty lines. */
-      q = p + strlen (p) - 1;
-      while (q > p && *q == '/')/* Zap trailing "/"s. */
-       *q-- = '\0';
-      if (change_dirs && next_name_is_dir == 0
-         && p[0] == '-' && p[1] == 'C' && p[2] == '\0')
-       {
-         next_name_is_dir = 1;
-         goto tryagain;
-       }
-      if (next_name_is_dir)
-       {
-         if (chdir (p) < 0)
-           msg_perror ("Can't change to directory %s", p);
-         next_name_is_dir = 0;
-         goto tryagain;
+         buffer[1] = *letter;
+         *out++ = xstrdup (buffer);
+         opt = find_argp_option (options, *letter);
+         if (opt && opt->arg)
+           {
+             if (in < argv + argc)
+               *out++ = *in++;
+             else
+               USAGE_ERROR ((0, 0, _("Old option `%c' requires an argument."),
+                             *letter));
+           }
        }
-#if 0
-      if (f_exclude && check_exclude (p))
-       goto tryagain;
-#endif
-      return un_quote_string (p);
-    }
-  return NULL;
-}
 
+      /* Copy all remaining options.  */
 
-/*
- * Close the name file, if any.
- */
-void
-name_close ()
-{
+      while (in < argv + argc)
+       *out++ = *in++;
+      *out = 0;
 
-  if (namef != NULL && namef != stdin)
-    fclose (namef);
-}
+      /* Replace the old option list by the new one.  */
 
+      argc = new_argc;
+      argv = new_argv;
+    }
 
-/*
- * Gather names in a list for scanning.
- * Could hash them later if we really care.
- *
- * If the names are already sorted to match the archive, we just
- * read them one by one.  name_gather reads the first one, and it
- * is called by name_match as appropriate to read the next ones.
- * At EOF, the last name read is just left in the buffer.
- * This option lets users of small machines extract an arbitrary
- * number of files by doing "tar t" and editing down the list of files.
- */
-void
-name_gather ()
-{
-  register char *p;
-  static struct name *namebuf; /* One-name buffer */
-  static namelen;
-  static char *chdir_name;
+  /* Parse all options and non-options as they appear.  */
 
-  if (f_sorted_names)
-    {
-      if (!namelen)
-       {
-         namelen = NAMSIZ;
-         namebuf = (struct name *) ck_malloc (sizeof (struct name) + NAMSIZ);
-       }
-      p = name_next (0);
-      if (p)
-       {
-         if (*p == '-' && p[1] == 'C' && p[2] == '\0')
-           {
-             chdir_name = name_next (0);
-             p = name_next (0);
-             if (!p)
-               {
-                 msg ("Missing file name after -C");
-                 exit (EX_ARGSBAD);
-               }
-             namebuf->change_dir = chdir_name;
-           }
-         namebuf->length = strlen (p);
-         if (namebuf->length >= namelen)
-           {
-             namebuf = (struct name *) ck_realloc (namebuf, sizeof (struct name) + namebuf->length);
-             namelen = namebuf->length;
-           }
-         strncpy (namebuf->name, p, namebuf->length);
-         namebuf->name[namebuf->length] = 0;
-         namebuf->next = (struct name *) NULL;
-         namebuf->found = 0;
-         namelist = namebuf;
-         namelast = namelist;
-       }
-      return;
-    }
+  prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
 
-  /* Non sorted names -- read them all in */
-  while (p = name_next (0))
-    addname (p);
-}
+  if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &idx, &args))
+    exit (TAREXIT_FAILURE);
 
-/*
- * Add a name to the namelist.
- */
-void
-addname (name)
-     char *name;               /* pointer to name */
-{
-  register int i;              /* Length of string */
-  register struct name *p;     /* Current struct pointer */
-  static char *chdir_name;
-  char *new_name ();
 
-  if (name[0] == '-' && name[1] == 'C' && name[2] == '\0')
+  /* Special handling for 'o' option:
+
+     GNU tar used to say "output old format".
+     UNIX98 tar says don't chown files after extracting (we use
+     "--no-same-owner" for this).
+
+     The old GNU tar semantics is retained when used with --create
+     option, otherwise UNIX98 semantics is assumed */
+
+  if (args.o_option)
     {
-      chdir_name = name_next (0);
-      name = name_next (0);
-      if (!chdir_name)
+      if (subcommand_option == CREATE_SUBCOMMAND)
        {
-         msg ("Missing file name after -C");
-         exit (EX_ARGSBAD);
+         /* GNU Tar <= 1.13 compatibility */
+         set_archive_format ("v7");
        }
-      if (chdir_name[0] != '/')
+      else
        {
-         char *path = ck_malloc (PATH_MAX);
-#if defined(__MSDOS__) || defined(HAVE_GETCWD) || defined(_POSIX_VERSION)
-         if (!getcwd (path, PATH_MAX))
-           {
-             msg ("Couldn't get current directory.");
-             exit (EX_SYSTEM);
-           }
-#else
-         char *getwd ();
-
-         if (!getwd (path))
-           {
-             msg ("Couldn't get current directory: %s", path);
-             exit (EX_SYSTEM);
-           }
-#endif
-         chdir_name = new_name (path, chdir_name);
-         free (path);
+         /* UNIX98 compatibility */
+         same_owner_option = -1;
        }
     }
 
-  if (name)
+  /* Handle operands after any "--" argument.  */
+  for (; idx < argc; idx++)
     {
-      i = strlen (name);
-      /*NOSTRICT*/
-      p = (struct name *) malloc ((unsigned) (sizeof (struct name) + i));
+      name_add_name (argv[idx], MAKE_INCL_OPTIONS (&args));
+      args.input_files = true;
     }
-  else
-    p = (struct name *) malloc ((unsigned) (sizeof (struct name)));
-  if (!p)
+
+  /* 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)
     {
-      if (name)
-       msg ("cannot allocate mem for name '%s'.", name);
+      if (args.pax_option)
+       archive_format = POSIX_FORMAT;
       else
-       msg ("cannot allocate mem for chdir record.");
-      exit (EX_SYSTEM);
+       archive_format = DEFAULT_ARCHIVE_FORMAT;
     }
-  p->next = (struct name *) NULL;
-  if (name)
+
+  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));
+
+  if (occurrence_option)
     {
-      p->fake = 0;
-      p->length = i;
-      strncpy (p->name, name, i);
-      p->name[i] = '\0';       /* Null term */
+      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")));
     }
-  else
-    p->fake = 1;
-  p->found = 0;
-  p->regexp = 0;               /* Assume not a regular expression */
-  p->firstch = 1;              /* Assume first char is literal */
-  p->change_dir = chdir_name;
-  p->dir_contents = 0;         /* JF */
-  if (name)
+
+  if (archive_names == 0)
     {
-      if (index (name, '*') || index (name, '[') || index (name, '?'))
-       {
-         p->regexp = 1;        /* No, it's a regexp */
-         if (name[0] == '*' || name[0] == '[' || name[0] == '?')
-           p->firstch = 0;     /* Not even 1st char literal */
-       }
-    }
+      /* If no archive file name given, try TAPE from the environment, or
+        else, DEFAULT_ARCHIVE from the configuration process.  */
 
-  if (namelast)
-    namelast->next = p;
-  namelast = p;
-  if (!namelist)
-    namelist = p;
-}
+      archive_names = 1;
+      archive_name_array[0] = getenv ("TAPE");
+      if (! archive_name_array[0])
+       archive_name_array[0] = DEFAULT_ARCHIVE;
+    }
 
-/*
- * Return nonzero if name P (from an archive) matches any name from
- * the namelist, zero if not.
- */
-int
-name_match (p)
-     register char *p;
-{
-  register struct name *nlp;
-  register int len;
+  /* Allow multiple archives only with `-M'.  */
 
-again:
-  if (0 == (nlp = namelist))   /* Empty namelist is easy */
-    return 1;
-  if (nlp->fake)
-    {
-      if (nlp->change_dir && chdir (nlp->change_dir))
-       msg_perror ("Can't change to directory %d", nlp->change_dir);
-      namelist = 0;
-      return 1;
-    }
-  len = strlen (p);
-  for (; nlp != 0; nlp = nlp->next)
-    {
-      /* If first chars don't match, quick skip */
-      if (nlp->firstch && nlp->name[0] != p[0])
-       continue;
+  if (archive_names > 1 && !multi_volume_option)
+    USAGE_ERROR ((0, 0,
+                 _("Multiple archive files require `-M' option")));
 
-      /* Regular expressions (shell globbing, actually). */
-      if (nlp->regexp)
-       {
-         if (fnmatch (nlp->name, p, FNM_LEADING_DIR) == 0)
-           {
-             nlp->found = 1;   /* Remember it matched */
-             if (f_startfile)
-               {
-                 free ((void *) namelist);
-                 namelist = 0;
-               }
-             if (nlp->change_dir && chdir (nlp->change_dir))
-               msg_perror ("Can't change to directory %s", nlp->change_dir);
-             return 1;         /* We got a match */
-           }
-         continue;
-       }
+  if (listed_incremental_option
+      && 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")));
 
-      /* Plain Old Strings */
-      if (nlp->length <= len   /* Archive len >= specified */
-         && (p[nlp->length] == '\0' || p[nlp->length] == '/')
-      /* Full match on file/dirname */
-         && strncmp (p, nlp->name, nlp->length) == 0)  /* Name compare */
+  if (volume_label_option)
+    {
+      if (archive_format == GNU_FORMAT || archive_format == OLDGNU_FORMAT)
        {
-         nlp->found = 1;       /* Remember it matched */
-         if (f_startfile)
-           {
-             free ((void *) namelist);
-             namelist = 0;
-           }
-         if (nlp->change_dir && chdir (nlp->change_dir))
-           msg_perror ("Can't change to directory %s", nlp->change_dir);
-         return 1;             /* We got a match */
+         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. */
     }
 
-  /*
-        * Filename from archive not found in namelist.
-        * If we have the whole namelist here, just return 0.
-        * Otherwise, read the next name in and compare it.
-        * If this was the last name, namelist->found will remain on.
-        * If not, we loop to compare the newly read name.
-        */
-  if (f_sorted_names && namelist->found)
+  if (verify_option)
     {
-      name_gather ();          /* Read one more */
-      if (!namelist->found)
-       goto again;
+      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")));
     }
-  return 0;
-}
 
-
-/*
- * Print the names of things in the namelist that were not matched.
- */
-void
-names_notfound ()
-{
-  register struct name *nlp, *next;
-  register char *p;
-
-  for (nlp = namelist; nlp != 0; nlp = next)
+  if (use_compress_program_option)
     {
-      next = nlp->next;
-      if (!nlp->found)
-       msg ("%s not found in archive", nlp->name);
-
-      /*
-                * We could free() the list, but the process is about
-                * to die anyway, so save some CPU time.  Amigas and
-                * other similarly broken software will need to waste
-                * the time, though.
-                */
-#ifdef amiga
-      if (!f_sorted_names)
-       free (nlp);
-#endif
+      if (multi_volume_option)
+       USAGE_ERROR ((0, 0, _("Cannot use multi-volume compressed archives")));
+      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")));
     }
-  namelist = (struct name *) NULL;
-  namelast = (struct name *) NULL;
 
-  if (f_sorted_names)
+  /* It is no harm to use --pax-option on non-pax archives in archive
+     reading mode. It may even be useful, since it allows to override
+     file attributes from tar headers. Therefore I allow such usage.
+     --gray */
+  if (args.pax_option
+      && archive_format != POSIX_FORMAT
+      && (subcommand_option != EXTRACT_SUBCOMMAND
+         || subcommand_option != DIFF_SUBCOMMAND
+         || subcommand_option != LIST_SUBCOMMAND))
+    USAGE_ERROR ((0, 0, _("--pax-option can be used only on POSIX archives")));
+
+  /* If ready to unlink hierarchies, so we are for simpler files.  */
+  if (recursive_unlink_option)
+    old_files_option = UNLINK_FIRST_OLD_FILES;
+
+  /* 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)
     {
-      while (0 != (p = name_next (1)))
-       msg ("%s not found in archive", p);
+      /* --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;
 
-/* These next routines were created by JF */
+  if (tape_length_option && tape_length_option < record_size)
+    USAGE_ERROR ((0, 0, _("Volume length cannot be less than record size")));
 
-void
-name_expand ()
-{
-  ;
-}
+  if (same_order_option && listed_incremental_option)
+    USAGE_ERROR ((0, 0, _("--preserve-order is not compatible with "
+                         "--listed-incremental")));
 
-/* This is like name_match(), except that it returns a pointer to the name
-   it matched, and doesn't set ->found  The caller will have to do that
-   if it wants to.  Oh, and if the namelist is empty, it returns 0, unlike
-   name_match(), which returns TRUE */
+  /* Forbid using -c with no input files whatsoever.  Check that `-f -',
+     explicit or implied, is used correctly.  */
 
-struct name *
-name_scan (p)
-     register char *p;
-{
-  register struct name *nlp;
-  register int len;
-
-again:
-  if (0 == (nlp = namelist))   /* Empty namelist is easy */
-    return 0;
-  len = strlen (p);
-  for (; nlp != 0; nlp = nlp->next)
+  switch (subcommand_option)
     {
-      /* If first chars don't match, quick skip */
-      if (nlp->firstch && nlp->name[0] != p[0])
-       continue;
+    case CREATE_SUBCOMMAND:
+      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;
 
-      /* Regular expressions */
-      if (nlp->regexp)
-       {
-         if (fnmatch (nlp->name, p, FNM_LEADING_DIR) == 0)
-           return nlp;         /* We got a match */
-         continue;
-       }
+    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++)
+       if (!strcmp (*archive_name_cursor, "-"))
+         request_stdin ("-f");
+      break;
 
-      /* Plain Old Strings */
-      if (nlp->length <= len   /* Archive len >= specified */
-         && (p[nlp->length] == '\0' || p[nlp->length] == '/')
-      /* Full match on file/dirname */
-         && strncmp (p, nlp->name, nlp->length) == 0)  /* Name compare */
-       return nlp;             /* We got a match */
+    case CAT_SUBCOMMAND:
+    case UPDATE_SUBCOMMAND:
+    case APPEND_SUBCOMMAND:
+      for (archive_name_cursor = archive_name_array;
+          archive_name_cursor < archive_name_array + archive_names;
+          archive_name_cursor++)
+       if (!strcmp (*archive_name_cursor, "-"))
+         USAGE_ERROR ((0, 0,
+                       _("Options `-Aru' are incompatible with `-f -'")));
+
+    default:
+      break;
     }
 
-  /*
-        * Filename from archive not found in namelist.
-        * If we have the whole namelist here, just return 0.
-        * Otherwise, read the next name in and compare it.
-        * If this was the last name, namelist->found will remain on.
-        * If not, we loop to compare the newly read name.
-        */
-  if (f_sorted_names && namelist->found)
+  /* Initialize stdlis */
+  if (index_file_name)
     {
-      name_gather ();          /* Read one more */
-      if (!namelist->found)
-       goto again;
+      stdlis = fopen (index_file_name, "w");
+      if (! stdlis)
+       open_error (index_file_name);
     }
-  return (struct name *) 0;
-}
+  else
+    stdlis = to_stdout_option ? stderr : stdout;
 
-/* This returns a name from the namelist which doesn't have ->found set.
-   It sets ->found before returning, so successive calls will find and return
-   all the non-found names in the namelist */
+  archive_name_cursor = archive_name_array;
 
-struct name *gnu_list_name;
+  /* Prepare for generating backup names.  */
 
-char *
-name_from_list ()
-{
-  if (!gnu_list_name)
-    gnu_list_name = namelist;
-  while (gnu_list_name && gnu_list_name->found)
-    gnu_list_name = gnu_list_name->next;
-  if (gnu_list_name)
+  if (args.backup_suffix_string)
+    simple_backup_suffix = xstrdup (args.backup_suffix_string);
+
+  if (backup_option)
     {
-      gnu_list_name->found++;
-      if (gnu_list_name->change_dir)
-       if (chdir (gnu_list_name->change_dir) < 0)
-         msg_perror ("can't chdir to %s", gnu_list_name->change_dir);
-      return gnu_list_name->name;
+      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;
     }
-  return (char *) 0;
-}
 
-void
-blank_name_list ()
-{
-  struct name *n;
+  checkpoint_finish_compile ();
 
-  gnu_list_name = 0;
-  for (n = namelist; n; n = n->next)
-    n->found = 0;
+  report_textual_dates (&args);
 }
 
-char *
-new_name (path, name)
-     char *path, *name;
+\f
+/* Tar proper.  */
+
+/* Main routine for tar.  */
+int
+main (int argc, char **argv)
 {
-  char *path_buf;
+  set_start_time ();
+  set_program_name (argv[0]);
 
-  path_buf = (char *) malloc (strlen (path) + strlen (name) + 2);
-  if (path_buf == 0)
-    {
-      msg ("Can't allocate memory for name '%s/%s", path, name);
-      exit (EX_SYSTEM);
-    }
-  (void) sprintf (path_buf, "%s/%s", path, name);
-  return path_buf;
-}
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
 
-/* returns non-zero if the luser typed 'y' or 'Y', zero otherwise. */
+  exit_failure = TAREXIT_FAILURE;
+  exit_status = TAREXIT_SUCCESS;
+  filename_terminator = '\n';
+  set_quoting_style (0, DEFAULT_QUOTING_STYLE);
 
-int
-confirm (action, file)
-     char *action, *file;
-{
-  int c, nl;
-  static FILE *confirm_file = 0;
-  extern FILE *msg_file;
-  extern char TTY_NAME[];
+  /* Make sure we have first three descriptors available */
+  stdopen ();
 
-  fprintf (msg_file, "%s %s?", action, file);
-  fflush (msg_file);
-  if (!confirm_file)
-    {
-      confirm_file = (archive == 0) ? fopen (TTY_NAME, "r") : stdin;
-      if (!confirm_file)
-       {
-         msg ("Can't read confirmation from user");
-         exit (EX_SYSTEM);
-       }
-    }
-  c = getc (confirm_file);
-  for (nl = c; nl != '\n' && nl != EOF; nl = getc (confirm_file))
-    ;
-  return (c == 'y' || c == 'Y');
-}
+  /* Pre-allocate a few structures.  */
 
-char *x_buffer = 0;
-int size_x_buffer;
-int free_x_buffer;
+  allocated_archive_names = 10;
+  archive_name_array =
+    xmalloc (sizeof (const char *) * allocated_archive_names);
+  archive_names = 0;
 
-char **exclude = 0;
-int size_exclude = 0;
-int free_exclude = 0;
+  obstack_init (&argv_stk);
 
-char **re_exclude = 0;
-int size_re_exclude = 0;
-int free_re_exclude = 0;
+  /* System V fork+wait does not work if SIGCHLD is ignored.  */
+  signal (SIGCHLD, SIG_DFL);
 
-void
-add_exclude (name)
-     char *name;
-{
-  /*   char *rname;*/
-  /*   char **tmp_ptr;*/
-  int size_buf;
+  /* Try to disable the ability to unlink a directory.  */
+  priv_set_remove_linkdir ();
 
-  un_quote_string (name);
-  size_buf = strlen (name);
+  /* Decode options.  */
 
-  if (x_buffer == 0)
-    {
-      x_buffer = (char *) ck_malloc (size_buf + 1024);
-      free_x_buffer = 1024;
-    }
-  else if (free_x_buffer <= size_buf)
-    {
-      char *old_x_buffer;
-      char **tmp_ptr;
-
-      old_x_buffer = x_buffer;
-      x_buffer = (char *) ck_realloc (x_buffer, size_x_buffer + 1024);
-      free_x_buffer = 1024;
-      for (tmp_ptr = exclude; tmp_ptr < exclude + size_exclude; tmp_ptr++)
-       *tmp_ptr = x_buffer + ((*tmp_ptr) - old_x_buffer);
-      for (tmp_ptr = re_exclude; tmp_ptr < re_exclude + size_re_exclude; tmp_ptr++)
-       *tmp_ptr = x_buffer + ((*tmp_ptr) - old_x_buffer);
-    }
+  decode_options (argc, argv);
 
-  if (is_regex (name))
-    {
-      if (free_re_exclude == 0)
-       {
-         re_exclude = (char **) (re_exclude ? ck_realloc (re_exclude, (size_re_exclude + 32) * sizeof (char *)): ck_malloc (sizeof (char *) * 32));
-         free_re_exclude += 32;
-       }
-      re_exclude[size_re_exclude] = x_buffer + size_x_buffer;
-      size_re_exclude++;
-      free_re_exclude--;
-    }
-  else
+  name_init ();
+
+  /* Main command execution.  */
+
+  if (volno_file_option)
+    init_volume_number ();
+
+  switch (subcommand_option)
     {
-      if (free_exclude == 0)
-       {
-         exclude = (char **) (exclude ? ck_realloc (exclude, (size_exclude + 32) * sizeof (char *)): ck_malloc (sizeof (char *) * 32));
-         free_exclude += 32;
-       }
-      exclude[size_exclude] = x_buffer + size_x_buffer;
-      size_exclude++;
-      free_exclude--;
+    case UNKNOWN_SUBCOMMAND:
+      USAGE_ERROR ((0, 0,
+                   _("You must specify one of the `-Acdtrux' or `--test-label'  options")));
+
+    case CAT_SUBCOMMAND:
+    case UPDATE_SUBCOMMAND:
+    case APPEND_SUBCOMMAND:
+      update_archive ();
+      break;
+
+    case DELETE_SUBCOMMAND:
+      delete_archive_members ();
+      break;
+
+    case CREATE_SUBCOMMAND:
+      create_archive ();
+      break;
+
+    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:
+      read_and (list_archive);
+      break;
+
+    case DIFF_SUBCOMMAND:
+      diff_init ();
+      read_and (diff_archive);
+      break;
+
+    case TEST_LABEL_SUBCOMMAND:
+      test_archive_label ();
     }
-  strcpy (x_buffer + size_x_buffer, name);
-  size_x_buffer += size_buf + 1;
-  free_x_buffer -= size_buf + 1;
+
+  if (totals_option)
+    print_total_stats ();
+
+  if (check_links_option)
+    check_links ();
+
+  if (volno_file_option)
+    closeout_volume_number ();
+
+  /* Dispose of allocated memory, and return.  */
+
+  free (archive_name_array);
+  name_term ();
+
+  if (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;
 }
 
 void
-add_exclude_file (file)
-     char *file;
+tar_stat_init (struct tar_stat_info *st)
 {
-  FILE *fp;
-  char buf[1024];
+  memset (st, 0, sizeof (*st));
+}
 
-  if (strcmp (file, "-"))
-    fp = fopen (file, "r");
+/* 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
-    /* Let's hope the person knows what they're doing. */
-    /* Using -X - -T - -f - will get you *REALLY* strange
-                  results. . . */
-    fp = stdin;
-
-  if (!fp)
-    {
-      msg_perror ("can't open %s", file);
-      exit (2);
-    }
-  while (fgets (buf, 1024, fp))
     {
-      /*               int size_buf;*/
-      char *end_str;
-
-      end_str = rindex (buf, '\n');
-      if (end_str)
-       *end_str = '\0';
-      add_exclude (buf);
-
+      close_diag (st->orig_file_name);
+      return false;
     }
-  fclose (fp);
 }
 
-int
-is_regex (str)
-     char *str;
+void
+tar_stat_destroy (struct tar_stat_info *st)
 {
-  return index (str, '*') || index (str, '[') || index (str, '?');
+  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));
 }
 
-/* Returns non-zero if the file 'name' should not be added/extracted */
+/* 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
-check_exclude (name)
-     char *name;
+tar_timespec_cmp (struct timespec a, struct timespec b)
 {
-  int n;
-  char *str;
-  extern char *strstr ();
+  if (!(FORMAT_MASK (current_format) & NS_PRECISION_FORMAT_MASK))
+    a.tv_nsec = b.tv_nsec = 0;
+  return timespec_cmp (a, b);
+}
 
-  for (n = 0; n < size_re_exclude; n++)
-    {
-      if (fnmatch (re_exclude[n], name, FNM_LEADING_DIR) == 0)
-       return 1;
-    }
-  for (n = 0; n < size_exclude; n++)
-    {
-      /* Accept the output from strstr only if it is the last
-                  part of the string.  There is certainly a faster way to
-                  do this. . . */
-      if ((str = strstr (name, exclude[n]))
-         && (str == name || str[-1] == '/')
-         && str[strlen (exclude[n])] == '\0')
-       return 1;
-    }
-  return 0;
+/* 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.098034 seconds and 4 git commands to generate.