re-mark 1.29b-2 as not yet uploaded (merge madness!)
[debian/tar] / src / names.c
index 1146020b2458f5a07fa5c534ea1511e596f3b703..037b869d7596c9c44476d28aca72acf46c8851c6 100644 (file)
@@ -1,7 +1,7 @@
 /* Various processing of names.
 
 /* Various processing of names.
 
-   Copyright (C) 1988, 1992, 1994, 1996, 1997, 1998, 1999, 2000, 2001,
-   2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
+   Copyright 1988, 1992, 1994, 1996-2001, 2003-2007, 2009, 2013-2016
+   Free Software Foundation, Inc.
 
    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
 
    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
    Public License for more details.
 
    You should have received a copy of the GNU General Public License along
    Public License for more details.
 
    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.  */
+   with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <system.h>
 
 #include <fnmatch.h>
 #include <hash.h>
 #include <quotearg.h>
 
 #include <system.h>
 
 #include <fnmatch.h>
 #include <hash.h>
 #include <quotearg.h>
+#include <wordsplit.h>
+#include <argp.h>
 
 #include "common.h"
 \f
 
 #include "common.h"
 \f
-/* User and group names.  */
+static void name_add_option (int option, const char *arg);
+static void name_add_dir (const char *name);
+static void name_add_file (const char *name);
+\f
+enum
+  {
+    EXCLUDE_BACKUPS_OPTION = 256,
+    EXCLUDE_CACHES_OPTION,
+    EXCLUDE_CACHES_UNDER_OPTION,
+    EXCLUDE_CACHES_ALL_OPTION,
+    EXCLUDE_OPTION,
+    EXCLUDE_IGNORE_OPTION,
+    EXCLUDE_IGNORE_RECURSIVE_OPTION,
+    EXCLUDE_TAG_OPTION,
+    EXCLUDE_TAG_UNDER_OPTION,
+    EXCLUDE_TAG_ALL_OPTION,
+    EXCLUDE_VCS_OPTION,
+    EXCLUDE_VCS_IGNORES_OPTION,
+    IGNORE_CASE_OPTION,
+    NO_IGNORE_CASE_OPTION,
+    ANCHORED_OPTION,
+    NO_ANCHORED_OPTION,
+    RECURSION_OPTION,
+    NO_RECURSION_OPTION,
+    UNQUOTE_OPTION,
+    NO_UNQUOTE_OPTION,
+    NO_VERBATIM_FILES_FROM_OPTION,
+    NO_WILDCARDS_MATCH_SLASH_OPTION,
+    NO_WILDCARDS_OPTION,
+    NULL_OPTION,
+    NO_NULL_OPTION,
+    VERBATIM_FILES_FROM_OPTION,
+    WILDCARDS_MATCH_SLASH_OPTION,
+    WILDCARDS_OPTION
+  };
+
+static struct argp_option names_options[] = {
+#define GRID 100
+  {NULL, 0, NULL, 0,
+   N_("Local file name 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; implies --verbatim-files-from"),
+      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 input file or member names (default)"), GRID+1 },
+  {"no-unquote", NO_UNQUOTE_OPTION, 0, 0,
+   N_("do not unquote input file or member names"), GRID+1 },
+  {"verbatim-files-from", VERBATIM_FILES_FROM_OPTION, 0, 0,
+   N_("-T reads file names verbatim (no option handling)"), GRID+1 },
+  {"no-verbatim-files-from", NO_VERBATIM_FILES_FROM_OPTION, 0, 0,
+   N_("-T treats file names starting with dash as options (default)"),
+      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-ignore", EXCLUDE_IGNORE_OPTION, N_("FILE"), 0,
+    N_("read exclude patterns for each directory from FILE, if it exists"),
+   GRID+1 },
+  {"exclude-ignore-recursive", EXCLUDE_IGNORE_RECURSIVE_OPTION, N_("FILE"), 0,
+    N_("read exclude patterns for each directory and its subdirectories "
+       "from FILE, if it exists"), 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-vcs-ignores", EXCLUDE_VCS_IGNORES_OPTION, NULL, 0,
+   N_("read exclude patterns from the VCS ignore files"), GRID+1 },
+  {"exclude-backups", EXCLUDE_BACKUPS_OPTION, NULL, 0,
+   N_("exclude backup and lock files"), GRID+1 },
+  {"recursion", RECURSION_OPTION, 0, 0,
+   N_("recurse into directories (default)"), GRID+1 },
+  {"no-recursion", NO_RECURSION_OPTION, 0, 0,
+   N_("avoid descending automatically in directories"), GRID+1 },
+#undef GRID
+
+#define GRID 120
+  {NULL, 0, NULL, 0,
+   N_("File name matching options (affect both exclude and include patterns):"),
+   GRID },
+  {"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 },
+  {"ignore-case", IGNORE_CASE_OPTION, 0, 0,
+   N_("ignore case"), 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 },
+  {"wildcards-match-slash", WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+   N_("wildcards match '/' (default for exclusion)"), GRID+1 },
+  {"no-wildcards-match-slash", NO_WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+   N_("wildcards do not match '/'"), GRID+1 },
+#undef GRID
+  
+  {NULL}
+};
 
 
-struct group *getgrnam ();
-struct passwd *getpwnam ();
-#if ! HAVE_DECL_GETPWUID
-struct passwd *getpwuid ();
-#endif
-#if ! HAVE_DECL_GETGRGID
-struct group *getgrgid ();
-#endif
+static bool
+is_file_selection_option (int key)
+{
+  struct argp_option *p;
+
+  for (p = names_options;
+       !(p->name == NULL && p->key == 0 && p->doc == NULL); p++)
+    if (p->key == key)
+      return true;
+  return false;
+}  
+\f
+/* Either NL or NUL, as decided by the --null option.  */
+static char filename_terminator = '\n';
+/* Treat file names read from -T input verbatim */
+static bool verbatim_files_from_option;
+
+static error_t
+names_parse_opt (int key, char *arg, struct argp_state *state)
+{
+  switch (key)
+    {
+    case 'C':
+      name_add_dir (arg);
+      break;
+
+    case 'T':
+      name_add_file (arg);
+      /* 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;
+
+    default:
+      if (is_file_selection_option (key))
+       name_add_option (key, arg);
+      else
+       return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+\f
+/* Wildcard matching settings */
+enum wildcards
+  {
+    default_wildcards, /* For exclusion == enable_wildcards,
+                         for inclusion == disable_wildcards */
+    disable_wildcards,
+    enable_wildcards
+  };
+
+static enum wildcards wildcards = default_wildcards;
+  /* Wildcard settings (--wildcards/--no-wildcards) */
+static int matching_flags = 0;
+  /* exclude_fnmatch options */
+static int include_anchored = EXCLUDE_ANCHORED;
+  /* Pattern anchoring options used for file inclusion */
+  
+#define EXCLUDE_OPTIONS                                                \
+  (((wildcards != disable_wildcards) ? EXCLUDE_WILDCARDS : 0)  \
+  | matching_flags                                             \
+  | recursion_option)
+
+#define INCLUDE_OPTIONS                                                    \
+  (((wildcards == enable_wildcards) ? EXCLUDE_WILDCARDS : 0)       \
+  | include_anchored                                               \
+  | matching_flags                                                 \
+  | recursion_option)
+\f
+static char const * const vcs_file_table[] = {
+  /* CVS: */
+  "CVS",
+  ".cvsignore",
+  /* RCS: */
+  "RCS",
+  /* SCCS: */
+  "SCCS",
+  /* SVN: */
+  ".svn",
+  /* git: */
+  ".git",
+  ".gitignore",
+  ".gitattributes",
+  ".gitmodules",
+  /* 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 opts)
+{
+  int i;
+
+  for (i = 0; fv[i]; i++)
+    add_exclude (excluded, fv[i], opts);
+}
+\f
+static void
+handle_file_selection_option (int key, const char *arg)
+{
+  switch (key)
+    {
+    case EXCLUDE_BACKUPS_OPTION:
+      add_exclude_array (backup_file_table, EXCLUDE_WILDCARDS);
+      break;
+
+    case EXCLUDE_OPTION:
+      add_exclude (excluded, arg, EXCLUDE_OPTIONS);
+      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_IGNORE_OPTION:
+      excfile_add (arg, EXCL_NON_RECURSIVE);
+      break;
+
+    case EXCLUDE_IGNORE_RECURSIVE_OPTION:
+      excfile_add (arg, EXCL_RECURSIVE);
+      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, 0);
+      break;
+
+    case EXCLUDE_VCS_IGNORES_OPTION:
+      exclude_vcs_ignores ();
+      break;
+
+    case RECURSION_OPTION:
+      recursion_option = FNM_LEADING_DIR;
+      break;
+
+    case NO_RECURSION_OPTION:
+      recursion_option = 0;
+      break;
+
+    case UNQUOTE_OPTION:
+      unquote_option = true;
+      break;
+
+    case NO_UNQUOTE_OPTION:
+      unquote_option = false;
+      break;
+
+    case NULL_OPTION:
+      filename_terminator = '\0';
+      verbatim_files_from_option = true;
+      break;
+
+    case NO_NULL_OPTION:
+      filename_terminator = '\n';
+      verbatim_files_from_option = false;
+      break;
+
+    case 'X':
+      if (add_exclude_file (add_exclude, excluded, arg, EXCLUDE_OPTIONS, '\n')
+         != 0)
+       {
+         int e = errno;
+         FATAL_ERROR ((0, e, "%s", quotearg_colon (arg)));
+       }
+      break;
+
+    case ANCHORED_OPTION:
+      matching_flags |= EXCLUDE_ANCHORED;
+      break;
+
+    case NO_ANCHORED_OPTION:
+      include_anchored = 0; /* Clear the default for comman line args */
+      matching_flags &= ~ EXCLUDE_ANCHORED;
+      break;
+
+    case IGNORE_CASE_OPTION:
+      matching_flags |= FNM_CASEFOLD;
+      break;
+
+    case NO_IGNORE_CASE_OPTION:
+      matching_flags &= ~ FNM_CASEFOLD;
+      break;
+
+    case WILDCARDS_OPTION:
+      wildcards = enable_wildcards;
+      break;
+
+    case NO_WILDCARDS_OPTION:
+      wildcards = disable_wildcards;
+      break;
+
+    case WILDCARDS_MATCH_SLASH_OPTION:
+      matching_flags &= ~ FNM_FILE_NAME;
+      break;
+
+    case NO_WILDCARDS_MATCH_SLASH_OPTION:
+      matching_flags |= FNM_FILE_NAME;
+      break;
+
+    case VERBATIM_FILES_FROM_OPTION:
+      verbatim_files_from_option = true;
+      break;
+
+    case NO_VERBATIM_FILES_FROM_OPTION:
+      verbatim_files_from_option = false;
+      break;
+
+    default:
+      FATAL_ERROR ((0, 0, "unhandled positional option %d", key));
+    }
+}
+
+static struct argp names_argp = {
+  names_options,
+  names_parse_opt,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+struct argp_child names_argp_children[] = {
+  { &names_argp, 0, "", 0 },
+  { NULL }
+};
+\f
+/* User and group names.  */
 
 /* Make sure you link with the proper libraries if you are running the
    Yellow Peril (thanks for the good laugh, Ian J.!), or, euh... NIS.
 
 /* Make sure you link with the proper libraries if you are running the
    Yellow Peril (thanks for the good laugh, Ian J.!), or, euh... NIS.
@@ -56,8 +443,6 @@ static char *cached_no_such_gname;
 static uid_t cached_no_such_uid;
 static gid_t cached_no_such_gid;
 
 static uid_t cached_no_such_uid;
 static gid_t cached_no_such_gid;
 
-static void register_individual_file (char const *name);
-
 /* Given UID, find the corresponding UNAME.  */
 void
 uid_to_uname (uid_t uid, char **uname)
 /* Given UID, find the corresponding UNAME.  */
 void
 uid_to_uname (uid_t uid, char **uname)
@@ -179,7 +564,7 @@ gname_to_gid (char const *gname, gid_t *gidp)
 }
 
 \f
 }
 
 \f
-struct name *
+static struct name *
 make_name (const char *file_name)
 {
   struct name *p = xzalloc (sizeof (*p));
 make_name (const char *file_name)
 {
   struct name *p = xzalloc (sizeof (*p));
@@ -190,7 +575,7 @@ make_name (const char *file_name)
   return p;
 }
 
   return p;
 }
 
-void
+static void
 free_name (struct name *p)
 {
   if (p)
 free_name (struct name *p)
 {
   if (p)
@@ -207,83 +592,135 @@ free_name (struct name *p)
 static struct name *namelist;  /* first name in list, if any */
 static struct name *nametail;  /* end of name list */
 
 static struct name *namelist;  /* first name in list, if any */
 static struct name *nametail;  /* end of name list */
 
-/* File name arguments are processed in two stages: first a 
-   name_array (see below) is filled, then the names from it
+/* File name arguments are processed in two stages: first a
+   name element list (see below) is filled, then the names from it
    are moved into the namelist.
 
    This awkward process is needed only to implement --same-order option,
    which is meant to help process large archives on machines with
    limited memory.  With this option on, namelist contains at most one
    entry, which diminishes the memory consumption.
    are moved into the namelist.
 
    This awkward process is needed only to implement --same-order option,
    which is meant to help process large archives on machines with
    limited memory.  With this option on, namelist contains at most one
    entry, which diminishes the memory consumption.
-   
+
    However, I very much doubt if we still need this -- Sergey */
 
    However, I very much doubt if we still need this -- Sergey */
 
-/* A name_array element contains entries of three types: */
+/* A name_list element contains entries of three types: */
 
 
-#define NELT_NAME  0   /* File name */
-#define NELT_CHDIR 1   /* Change directory request */
-#define NELT_FMASK 2   /* Change fnmatch options request */
+enum nelt_type
+  {
+    NELT_NAME,   /* File name */
+    NELT_CHDIR,  /* Change directory request */
+    NELT_FILE,   /* Read file names from that file */
+    NELT_NOOP,   /* No operation */
+    NELT_OPTION  /* Filename-selection option */
+  };
 
 struct name_elt        /* A name_array element. */
 {
 
 struct name_elt        /* A name_array element. */
 {
-  char type;           /* Element type, see NELT_* constants above */
+  struct name_elt *next, *prev;
+  enum nelt_type type; /* Element type, see NELT_* constants above */
   union
   {
     const char *name;  /* File or directory name */
   union
   {
     const char *name;  /* File or directory name */
-    int matching_flags;/* fnmatch options if type == NELT_FMASK */ 
+    struct             /* File, if type == NELT_FILE */
+    {
+      const char *name;/* File name */
+      size_t line;     /* Input line number */
+      int term;        /* File name terminator in the list */
+      bool verbatim;   /* Verbatim handling of file names: no white-space
+                         trimming, no option processing */
+      FILE *fp;
+    } file;
+    struct
+    {
+      int option;
+      char const *arg;
+    } opt; /* NELT_OPTION */
   } v;
 };
 
   } v;
 };
 
-static struct name_elt *name_array;  /* store an array of names */
-static size_t allocated_names;  /* how big is the array? */
-static size_t names;            /* how many entries does it have? */
-static size_t name_index;       /* how many of the entries have we scanned? */
+static struct name_elt *name_head;  /* store a list of names */
+size_t name_count;                 /* how many of the entries are names? */
 
 
-/* Check the size of name_array, reallocating it as necessary.  */
-static void
-check_name_alloc ()
+static struct name_elt *
+name_elt_alloc (void)
 {
 {
-  if (names == allocated_names)
+  struct name_elt *elt;
+
+  elt = xmalloc (sizeof (*elt));
+  if (!name_head)
     {
     {
-      if (allocated_names == 0)
-       allocated_names = 10; /* Set initial allocation */
-      name_array = x2nrealloc (name_array, &allocated_names,
-                              sizeof (name_array[0]));
+      name_head = elt;
+      name_head->prev = name_head->next = NULL;
+      name_head->type = NELT_NOOP;
+      elt = xmalloc (sizeof (*elt));
     }
     }
+
+  elt->prev = name_head->prev;
+  if (name_head->prev)
+    name_head->prev->next = elt;
+  elt->next = name_head;
+  name_head->prev = elt;
+  return elt;
 }
 
 }
 
-/* Add to name_array the file NAME with fnmatch options MATCHING_FLAGS */
+static void
+name_list_adjust (void)
+{
+  if (name_head)
+    while (name_head->prev)
+      name_head = name_head->prev;
+}
+
+static void
+name_list_advance (void)
+{
+  struct name_elt *elt = name_head;
+  name_head = elt->next;
+  if (name_head)
+    name_head->prev = NULL;
+  free (elt);
+}
+
+
+/* Add to name_array the file NAME with fnmatch options MATFLAGS */
 void
 void
-name_add_name (const char *name, int matching_flags)
+name_add_name (const char *name)
 {
 {
-  static int prev_flags = 0; /* FIXME: Or EXCLUDE_ANCHORED? */
-  struct name_elt *ep;
+  struct name_elt *ep = name_elt_alloc ();
 
 
-  check_name_alloc ();
-  ep = &name_array[names++];
-  if (prev_flags != matching_flags)
-    {
-      ep->type = NELT_FMASK;
-      ep->v.matching_flags = matching_flags;
-      prev_flags = matching_flags;
-      check_name_alloc ();
-      ep = &name_array[names++];
-    }
   ep->type = NELT_NAME;
   ep->v.name = name;
   ep->type = NELT_NAME;
   ep->v.name = name;
+  name_count++;
+}
+
+static void
+name_add_option (int option, const char *arg)
+{
+  struct name_elt *elt = name_elt_alloc ();
+  elt->type = NELT_OPTION;
+  elt->v.opt.option = option;
+  elt->v.opt.arg = arg;
 }
 
 /* Add to name_array a chdir request for the directory NAME */
 }
 
 /* Add to name_array a chdir request for the directory NAME */
-void
+static void
 name_add_dir (const char *name)
 {
 name_add_dir (const char *name)
 {
-  struct name_elt *ep;
-  check_name_alloc ();
-  ep = &name_array[names++];
+  struct name_elt *ep = name_elt_alloc ();
   ep->type = NELT_CHDIR;
   ep->v.name = name;
   ep->type = NELT_CHDIR;
   ep->v.name = name;
-}  
+}
 
 
+static void
+name_add_file (const char *name)
+{
+  struct name_elt *ep = name_elt_alloc ();
+
+  ep->type = NELT_FILE;
+  ep->v.file.name = name;
+  ep->v.file.line = 0;
+  ep->v.file.fp = NULL;
+}
 \f
 /* Names from external name file.  */
 
 \f
 /* Names from external name file.  */
 
@@ -297,81 +734,282 @@ name_init (void)
 {
   name_buffer = xmalloc (NAME_FIELD_SIZE + 2);
   name_buffer_length = NAME_FIELD_SIZE;
 {
   name_buffer = xmalloc (NAME_FIELD_SIZE + 2);
   name_buffer_length = NAME_FIELD_SIZE;
+  name_list_adjust ();
 }
 
 void
 name_term (void)
 {
   free (name_buffer);
 }
 
 void
 name_term (void)
 {
   free (name_buffer);
-  free (name_array);
 }
 }
+\f
+/* Prevent recursive inclusion of the same file */
+struct file_id_list
+{
+  struct file_id_list *next;
+  ino_t ino;
+  dev_t dev;
+  const char *from_file;
+};
 
 
-static int matching_flags; /* exclude_fnmatch options */
+static struct file_id_list *file_id_list;
 
 
-/* Get the next NELT_NAME element from name_array.  Result is in
-   static storage and can't be relied upon across two calls.
+/* Return the name of the file from which the file names and options
+   are being read.
+*/
+static const char *
+file_list_name (void)
+{
+  struct name_elt *elt;
 
 
-   If CHANGE_DIRS is true, treat any entries of type NELT_CHDIR as
-   the request to change to the given directory.
-   
-   Entries of type NELT_FMASK cause updates of the matching_flags
-   value. */
-struct name_elt *
-name_next_elt (int change_dirs)
+  for (elt = name_head; elt; elt = elt->next)
+    if (elt->type == NELT_FILE && elt->v.file.fp)
+      return elt->v.file.name;
+  return _("command line");
+}
+
+static int
+add_file_id (const char *filename)
 {
 {
-  static struct name_elt entry;
-  const char *source;
-  char *cursor;
+  struct file_id_list *p;
+  struct stat st;
+  const char *reading_from;
+
+  if (stat (filename, &st))
+    stat_fatal (filename);
+  reading_from = file_list_name ();
+  for (p = file_id_list; p; p = p->next)
+    if (p->ino == st.st_ino && p->dev == st.st_dev)
+      {
+       int oldc = set_char_quoting (NULL, ':', 1);
+       ERROR ((0, 0,
+               _("%s: file list requested from %s already read from %s"),
+               quotearg_n (0, filename),
+               reading_from, p->from_file));
+       set_char_quoting (NULL, ':', oldc);
+       return 1;
+      }
+  p = xmalloc (sizeof *p);
+  p->next = file_id_list;
+  p->ino = st.st_ino;
+  p->dev = st.st_dev;
+  p->from_file = reading_from;
+  file_id_list = p;
+  return 0;
+}
+
+/* Chop trailing slashes.  */
+static void
+chopslash (char *str)
+{
+  char *p = str + strlen (str) - 1;
+  while (p > str && ISSLASH (*p))
+    *p-- = '\0';
+}
+\f
+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.
+ */
+static enum read_file_list_state
+read_name_from_file (struct name_elt *ent)
+{
+  int c;
+  size_t counter = 0;
+  FILE *fp = ent->v.file.fp;
+  int term = ent->v.file.term;
 
 
-  while (name_index != names)
+  ++ent->v.file.line;
+  for (c = getc (fp); c != EOF && c != term; c = getc (fp))
     {
     {
-      struct name_elt *ep;
-      size_t source_len;
-      
-      ep = &name_array[name_index++];
-      if (ep->type == NELT_FMASK)
+      if (counter == name_buffer_length)
+       name_buffer = x2realloc (name_buffer, &name_buffer_length);
+      name_buffer[counter++] = c;
+      if (c == 0)
        {
        {
-         matching_flags = ep->v.matching_flags;
-         continue;
+         /* We have read a zero separator. The file possibly is
+            zero-separated */
+         return file_list_zero;
        }
        }
-      
-      source = ep->v.name;
-      source_len = strlen (source);
-      if (name_buffer_length < source_len)
+    }
+
+  if (counter == 0 && c != EOF)
+    return file_list_skip;
+
+  if (counter == name_buffer_length)
+    name_buffer = x2realloc (name_buffer, &name_buffer_length);
+  name_buffer[counter] = 0;
+  chopslash (name_buffer);
+  return (counter == 0 && c == EOF) ? file_list_end : file_list_success;
+}
+
+static int
+handle_option (const char *str, struct name_elt const *ent)
+{
+  struct wordsplit ws;
+  int i;
+  struct option_locus loc;
+  
+  while (*str && isspace (*str))
+    ++str;
+  if (*str != '-')
+    return 1;
+
+  ws.ws_offs = 1;
+  if (wordsplit (str, &ws, WRDSF_DEFFLAGS|WRDSF_DOOFFS))
+    FATAL_ERROR ((0, 0, _("cannot split string '%s': %s"),
+                 str, wordsplit_strerror (&ws)));
+  ws.ws_wordv[0] = (char *) program_name;
+  loc.source = OPTS_FILE;
+  loc.name = ent->v.file.name;
+  loc.line = ent->v.file.line;
+  more_options (ws.ws_wordc+ws.ws_offs, ws.ws_wordv, &loc);
+  for (i = 0; i < ws.ws_wordc+ws.ws_offs; i++)
+    ws.ws_wordv[i] = NULL;
+
+  wordsplit_free (&ws);
+  return 0;
+}
+
+static int
+read_next_name (struct name_elt *ent, struct name_elt *ret)
+{
+  if (!ent->v.file.fp)
+    {
+      if (!strcmp (ent->v.file.name, "-"))
        {
        {
-         do
+         request_stdin ("-T");
+         ent->v.file.fp = stdin;
+       }
+      else
+       {
+         if (add_file_id (ent->v.file.name))
            {
            {
-             name_buffer_length *= 2;
-             if (! name_buffer_length)
-               xalloc_die ();
+             name_list_advance ();
+             return 1;
            }
            }
-         while (name_buffer_length < source_len);
-
-         free (name_buffer);
-         name_buffer = xmalloc (name_buffer_length + 2);
+         if ((ent->v.file.fp = fopen (ent->v.file.name, "r")) == NULL)
+           open_fatal (ent->v.file.name);
        }
        }
-      strcpy (name_buffer, source);
+      ent->v.file.term = filename_terminator;
+      ent->v.file.verbatim = verbatim_files_from_option;
+    }
 
 
-      /* Zap trailing slashes.  */
+  while (1)
+    {
+      switch (read_name_from_file (ent))
+       {
+       case file_list_skip:
+         continue;
 
 
-      cursor = name_buffer + strlen (name_buffer) - 1;
-      while (cursor > name_buffer && ISSLASH (*cursor))
-       *cursor-- = '\0';
+       case file_list_zero:
+         WARNOPT (WARN_FILENAME_WITH_NULS,
+                  (0, 0, N_("%s: file name read contains nul character"),
+                   quotearg_colon (ent->v.file.name)));
+         ent->v.file.term = 0;
+         /* fall through */
+       case file_list_success:
+         if (unquote_option)
+           unquote_string (name_buffer);
+         if (!ent->v.file.verbatim && handle_option (name_buffer, ent) == 0)
+           {
+             name_list_adjust ();
+             return 1;
+           }
+         ret->type = NELT_NAME;
+         ret->v.name = name_buffer;
+         return 0;
 
 
-      if (change_dirs && ep->type == NELT_CHDIR)
+       case file_list_end:
+         if (strcmp (ent->v.file.name, "-"))
+           fclose (ent->v.file.fp);
+         ent->v.file.fp = NULL;
+         name_list_advance ();
+         return 1;
+       }
+    }
+}
+\f
+static void
+copy_name (struct name_elt *ep)
+{
+  const char *source;
+  size_t source_len;
+
+  source = ep->v.name;
+  source_len = strlen (source);
+  if (name_buffer_length < source_len)
+    {
+      do
        {
        {
-         if (chdir (name_buffer) < 0)
-           chdir_fatal (name_buffer);
+         name_buffer_length *= 2;
+         if (! name_buffer_length)
+           xalloc_die ();
        }
        }
-      else
+      while (name_buffer_length < source_len);
+
+      free (name_buffer);
+      name_buffer = xmalloc(name_buffer_length + 2);
+    }
+  strcpy (name_buffer, source);
+  chopslash (name_buffer);
+}
+
+\f
+/* Get the next NELT_NAME element from name_array.  Result is in
+   static storage and can't be relied upon across two calls.
+
+   If CHANGE_DIRS is true, treat any entries of type NELT_CHDIR as
+   the request to change to the given directory.
+
+*/
+static struct name_elt *
+name_next_elt (int change_dirs)
+{
+  static struct name_elt entry;
+  struct name_elt *ep;
+
+  while ((ep = name_head) != NULL)
+    {
+      switch (ep->type)
        {
        {
+       case NELT_NOOP:
+         name_list_advance ();
+         break;
+
+       case NELT_FILE:
+         if (read_next_name (ep, &entry) == 0)
+           return &entry;
+         continue;
+
+       case NELT_CHDIR:
+         if (change_dirs)
+           {
+             chdir_do (chdir_arg (xstrdup (ep->v.name)));
+             name_list_advance ();
+             break;
+           }
+         /* fall through */
+       case NELT_NAME:
+         copy_name (ep);
          if (unquote_option)
            unquote_string (name_buffer);
          if (unquote_option)
            unquote_string (name_buffer);
-         if (incremental_option)
-           register_individual_file (name_buffer);
          entry.type = ep->type;
          entry.v.name = name_buffer;
          entry.type = ep->type;
          entry.v.name = name_buffer;
+         name_list_advance ();
          return &entry;
          return &entry;
+
+       case NELT_OPTION:
+         handle_file_selection_option (ep->v.opt.option, ep->v.opt.arg);
+         name_list_advance ();
+         continue;
        }
     }
 
        }
     }
 
@@ -417,11 +1055,11 @@ name_gather (void)
          buffer->change_dir = change_dir;
          buffer->next = 0;
          buffer->found_count = 0;
          buffer->change_dir = change_dir;
          buffer->next = 0;
          buffer->found_count = 0;
-         buffer->matching_flags = matching_flags;
+         buffer->matching_flags = INCLUDE_OPTIONS;
          buffer->directory = NULL;
          buffer->parent = NULL;
          buffer->cmdline = true;
          buffer->directory = NULL;
          buffer->parent = NULL;
          buffer->cmdline = true;
-         
+
          namelist = nametail = buffer;
        }
       else if (change_dir)
          namelist = nametail = buffer;
        }
       else if (change_dir)
@@ -459,7 +1097,7 @@ addname (char const *string, int change_dir, bool cmdline, struct name *parent)
   name->prev = nametail;
   name->next = NULL;
   name->found_count = 0;
   name->prev = nametail;
   name->next = NULL;
   name->found_count = 0;
-  name->matching_flags = matching_flags;
+  name->matching_flags = INCLUDE_OPTIONS;
   name->change_dir = change_dir;
   name->directory = NULL;
   name->parent = parent;
   name->change_dir = change_dir;
   name->directory = NULL;
   name->parent = parent;
@@ -519,7 +1157,7 @@ name_match (const char *file_name)
 
       if (!cursor)
        return true;
 
       if (!cursor)
        return true;
-      
+
       if (cursor->name[0] == 0)
        {
          chdir_do (cursor->change_dir);
       if (cursor->name[0] == 0)
        {
          chdir_do (cursor->change_dir);
@@ -589,12 +1227,15 @@ all_names_found (struct tar_stat_info *p)
   return true;
 }
 
   return true;
 }
 
-static void
+static int
 regex_usage_warning (const char *name)
 {
   static int warned_once = 0;
 
 regex_usage_warning (const char *name)
 {
   static int warned_once = 0;
 
-  if (warn_regex_usage && fnmatch_pattern_has_wildcards (name, 0))
+  /* Warn about implicit use of the wildcards in command line arguments.
+     (Default for tar prior to 1.15.91, but changed afterwards) */
+  if (wildcards == default_wildcards
+      && fnmatch_pattern_has_wildcards (name, 0))
     {
       warned_once = 1;
       WARN ((0, 0,
     {
       warned_once = 1;
       WARN ((0, 0,
@@ -603,6 +1244,7 @@ regex_usage_warning (const char *name)
             _("Use --wildcards to enable pattern matching,"
               " or --no-wildcards to suppress this warning")));
     }
             _("Use --wildcards to enable pattern matching,"
               " or --no-wildcards to suppress this warning")));
     }
+  return warned_once;
 }
 
 /* Print the names of things in the namelist that were not matched.  */
 }
 
 /* Print the names of things in the namelist that were not matched.  */
@@ -615,12 +1257,11 @@ names_notfound (void)
     if (!WASFOUND (cursor) && cursor->name[0])
       {
        regex_usage_warning (cursor->name);
     if (!WASFOUND (cursor) && cursor->name[0])
       {
        regex_usage_warning (cursor->name);
-       if (cursor->found_count == 0)
-         ERROR ((0, 0, _("%s: Not found in archive"),
-                 quotearg_colon (cursor->name)));
-       else
-         ERROR ((0, 0, _("%s: Required occurrence not found in archive"),
-                 quotearg_colon (cursor->name)));  
+       ERROR ((0, 0,
+               (cursor->found_count == 0) ?
+                    _("%s: Not found in archive") :
+                    _("%s: Required occurrence not found in archive"),
+               quotearg_colon (cursor->name)));
       }
 
   /* Don't bother freeing the name list; we're about to exit.  */
       }
 
   /* Don't bother freeing the name list; we're about to exit.  */
@@ -639,14 +1280,50 @@ names_notfound (void)
        }
     }
 }
        }
     }
 }
+
+void
+label_notfound (void)
+{
+  struct name const *cursor;
+
+  if (!namelist)
+    return;
+
+  for (cursor = namelist; cursor; cursor = cursor->next)
+    if (WASFOUND (cursor))
+      return;
+
+  if (verbose_option)
+    error (0, 0, _("Archive label mismatch"));
+  set_exit_status (TAREXIT_DIFFERS);
+
+  for (cursor = namelist; cursor; cursor = cursor->next)
+    {
+      if (regex_usage_warning (cursor->name))
+       break;
+    }
+
+  /* Don't bother freeing the name list; we're about to exit.  */
+  namelist = NULL;
+  nametail = NULL;
+
+  if (same_order_option)
+    {
+      const char *name;
+
+      while ((name = name_next (1)) != NULL
+            && regex_usage_warning (name) == 0)
+       ;
+    }
+}
 \f
 /* Sorting name lists.  */
 
 /* Sort *singly* linked LIST of names, of given LENGTH, using COMPARE
    to order names.  Return the sorted list.  Note that after calling
 \f
 /* Sorting name lists.  */
 
 /* Sort *singly* linked LIST of names, of given LENGTH, using COMPARE
    to order names.  Return the sorted list.  Note that after calling
-   this function, the `prev' links in list elements are messed up.
-   
-   Apart from the type `struct name' and the definition of SUCCESSOR,
+   this function, the 'prev' links in list elements are messed up.
+
+   Apart from the type 'struct name' and the definition of SUCCESSOR,
    this is a generic list-sorting function, but it's too painful to
    make it both generic and portable
    in C.  */
    this is a generic list-sorting function, but it's too painful to
    make it both generic and portable
    in C.  */
@@ -752,17 +1429,15 @@ compare_names (struct name const *n1, struct name const *n2)
 }
 
 \f
 }
 
 \f
-/* Add all the dirs under NAME, which names a directory, to the namelist.
-   If any of the files is a directory, recurse on the subdirectory.
-   DEVICE is the device not to leave, if the -l option is specified.
-   CMDLINE is true, if the NAME appeared on the command line. */
+/* Add all the dirs under ST to the namelist NAME, descending the
+   directory hierarchy recursively.  */
 
 static void
 
 static void
-add_hierarchy_to_namelist (struct name *name, dev_t device, bool cmdline)
+add_hierarchy_to_namelist (struct tar_stat_info *st, struct name *name)
 {
   const char *buffer;
 {
   const char *buffer;
-  
-  name_fill_directory (name, device, cmdline);
+
+  name->directory = scan_directory (st);
   buffer = directory_contents (name->directory);
   if (buffer)
     {
   buffer = directory_contents (name->directory);
   if (buffer)
     {
@@ -790,6 +1465,8 @@ add_hierarchy_to_namelist (struct name *name, dev_t device, bool cmdline)
          if (*string == 'D')
            {
              struct name *np;
          if (*string == 'D')
            {
              struct name *np;
+             struct tar_stat_info subdir;
+             int subfd;
 
              if (allocated_length <= name_length + string_length)
                {
 
              if (allocated_length <= name_length + string_length)
                {
@@ -810,7 +1487,38 @@ add_hierarchy_to_namelist (struct name *name, dev_t device, bool cmdline)
              else
                child_tail->sibling = np;
              child_tail = np;
              else
                child_tail->sibling = np;
              child_tail = np;
-             add_hierarchy_to_namelist (np, device, false);
+
+             tar_stat_init (&subdir);
+             subdir.parent = st;
+             if (st->fd < 0)
+               {
+                 subfd = -1;
+                 errno = - st->fd;
+               }
+             else
+               subfd = subfile_open (st, string + 1,
+                                     open_read_flags | O_DIRECTORY);
+             if (subfd < 0)
+               open_diag (namebuf);
+             else
+               {
+                 subdir.fd = subfd;
+                 if (fstat (subfd, &subdir.stat) != 0)
+                   stat_diag (namebuf);
+                 else if (! (O_DIRECTORY || S_ISDIR (subdir.stat.st_mode)))
+                   {
+                     errno = ENOTDIR;
+                     open_diag (namebuf);
+                   }
+                 else
+                   {
+                     subdir.orig_file_name = xstrdup (namebuf);
+                     add_hierarchy_to_namelist (&subdir, np);
+                     restore_parent_fd (&subdir);
+                   }
+               }
+
+             tar_stat_destroy (&subdir);
            }
        }
 
            }
        }
 
@@ -838,7 +1546,7 @@ name_compare (void const *entry1, void const *entry2)
 }
 
 \f
 }
 
 \f
-/* Rebase `name' member of CHILD and all its siblings to
+/* Rebase 'name' member of CHILD and all its siblings to
    the new PARENT. */
 static void
 rebase_child_list (struct name *child, struct name *parent)
    the new PARENT. */
 static void
 rebase_child_list (struct name *child, struct name *parent)
@@ -846,7 +1554,7 @@ rebase_child_list (struct name *child, struct name *parent)
   size_t old_prefix_len = child->parent->length;
   size_t new_prefix_len = parent->length;
   char *new_prefix = parent->name;
   size_t old_prefix_len = child->parent->length;
   size_t new_prefix_len = parent->length;
   char *new_prefix = parent->name;
-  
+
   for (; child; child = child->sibling)
     {
       size_t size = child->length - old_prefix_len + new_prefix_len;
   for (; child; child = child->sibling)
     {
       size_t size = child->length - old_prefix_len + new_prefix_len;
@@ -858,7 +1566,7 @@ rebase_child_list (struct name *child, struct name *parent)
       child->length = size;
 
       rebase_directory (child->directory,
       child->length = size;
 
       rebase_directory (child->directory,
-                       child->parent->name, old_prefix_len, 
+                       child->parent->name, old_prefix_len,
                        new_prefix, new_prefix_len);
     }
 }
                        new_prefix, new_prefix_len);
     }
 }
@@ -871,11 +1579,10 @@ void
 collect_and_sort_names (void)
 {
   struct name *name;
 collect_and_sort_names (void)
 {
   struct name *name;
-  struct name *next_name, *prev_name;
+  struct name *next_name, *prev_name = NULL;
   int num_names;
   int num_names;
-  struct stat statbuf;
   Hash_table *nametab;
   Hash_table *nametab;
-  
+
   name_gather ();
 
   if (!namelist)
   name_gather ();
 
   if (!namelist)
@@ -903,10 +1610,12 @@ collect_and_sort_names (void)
 
       read_directory_file ();
     }
 
       read_directory_file ();
     }
-  
+
   num_names = 0;
   for (name = namelist; name; name = name->next, num_names++)
     {
   num_names = 0;
   for (name = namelist; name; name = name->next, num_names++)
     {
+      struct tar_stat_info st;
+
       if (name->found_count || name->directory)
        continue;
       if (name->matching_flags & EXCLUDE_WILDCARDS)
       if (name->found_count || name->directory)
        continue;
       if (name->matching_flags & EXCLUDE_WILDCARDS)
@@ -918,28 +1627,44 @@ collect_and_sort_names (void)
       if (name->name[0] == 0)
        continue;
 
       if (name->name[0] == 0)
        continue;
 
-      if (deref_stat (dereference_option, name->name, &statbuf) != 0)
+      tar_stat_init (&st);
+
+      if (deref_stat (name->name, &st.stat) != 0)
        {
          stat_diag (name->name);
          continue;
        }
        {
          stat_diag (name->name);
          continue;
        }
-      if (S_ISDIR (statbuf.st_mode))
+      if (S_ISDIR (st.stat.st_mode))
        {
        {
-         name->found_count++;
-         add_hierarchy_to_namelist (name, statbuf.st_dev, true);
+         int dir_fd = openat (chdir_fd, name->name,
+                              open_read_flags | O_DIRECTORY);
+         if (dir_fd < 0)
+           open_diag (name->name);
+         else
+           {
+             st.fd = dir_fd;
+             if (fstat (dir_fd, &st.stat) != 0)
+               stat_diag (name->name);
+             else if (O_DIRECTORY || S_ISDIR (st.stat.st_mode))
+               {
+                 st.orig_file_name = xstrdup (name->name);
+                 name->found_count++;
+                 add_hierarchy_to_namelist (&st, name);
+               }
+           }
        }
        }
+
+      tar_stat_destroy (&st);
     }
 
   namelist = merge_sort (namelist, num_names, compare_names);
 
   num_names = 0;
     }
 
   namelist = merge_sort (namelist, num_names, compare_names);
 
   num_names = 0;
-  nametab = hash_initialize (0, 0,
-                            name_hash,
-                            name_compare, NULL);
+  nametab = hash_initialize (0, 0, name_hash, name_compare, NULL);
   for (name = namelist; name; name = next_name)
     {
       next_name = name->next;
   for (name = namelist; name; name = next_name)
     {
       next_name = name->next;
-      name->caname = normalize_filename (name->name);
+      name->caname = normalize_filename (name->change_dir, name->name);
       if (prev_name)
        {
          struct name *p = hash_lookup (nametab, name);
       if (prev_name)
        {
          struct name *p = hash_lookup (nametab, name);
@@ -950,6 +1675,7 @@ collect_and_sort_names (void)
                {
                  if (p->child)
                    rebase_child_list (p->child, name);
                {
                  if (p->child)
                    rebase_child_list (p->child, name);
+                 hash_delete (nametab, name);
                  /* FIXME: remove_directory (p->caname); ? */
                  remname (p);
                  free_name (p);
                  /* FIXME: remove_directory (p->caname); ? */
                  remname (p);
                  free_name (p);
@@ -1024,7 +1750,7 @@ name_scan (const char *file_name)
 struct name *gnu_list_name;
 
 struct name const *
 struct name *gnu_list_name;
 
 struct name const *
-name_from_list ()
+name_from_list (void)
 {
   if (!gnu_list_name)
     gnu_list_name = namelist;
 {
   if (!gnu_list_name)
     gnu_list_name = namelist;
@@ -1050,49 +1776,21 @@ blank_name_list (void)
     name->found_count = 0;
 }
 
     name->found_count = 0;
 }
 
-/* Yield a newly allocated file name consisting of FILE_NAME concatenated to
-   NAME, with an intervening slash if FILE_NAME does not already end in one. */
+/* Yield a newly allocated file name consisting of DIR_NAME concatenated to
+   NAME, with an intervening slash if DIR_NAME does not already end in one. */
 char *
 char *
-new_name (const char *file_name, const char *name)
+make_file_name (const char *directory_name, const char *name)
 {
 {
-  size_t file_name_len = strlen (file_name);
-  size_t namesize = strlen (name) + 1;
-  int slash = file_name_len && ! ISSLASH (file_name[file_name_len - 1]);
-  char *buffer = xmalloc (file_name_len + slash + namesize);
-  memcpy (buffer, file_name, file_name_len);
-  buffer[file_name_len] = '/';
-  memcpy (buffer + file_name_len + slash, name, namesize);
+  size_t dirlen = strlen (directory_name);
+  size_t namelen = strlen (name) + 1;
+  int slash = dirlen && ! ISSLASH (directory_name[dirlen - 1]);
+  char *buffer = xmalloc (dirlen + slash + namelen);
+  memcpy (buffer, directory_name, dirlen);
+  buffer[dirlen] = '/';
+  memcpy (buffer + dirlen + slash, name, namelen);
   return buffer;
 }
 
   return buffer;
 }
 
-/* Return nonzero if file NAME is excluded.  */
-bool
-excluded_name (char const *name)
-{
-  return excluded_file_name (excluded, name + FILE_SYSTEM_PREFIX_LEN (name));
-}
-\f
-static Hash_table *individual_file_table;
-
-static void
-register_individual_file (char const *name)
-{
-  struct stat st;
-  
-  if (deref_stat (dereference_option, name, &st) != 0)
-    return; /* Will be complained about later */
-  if (S_ISDIR (st.st_mode))
-    return;
-  
-  hash_string_insert (&individual_file_table, name);
-}
-
-bool
-is_individual_file (char const *name)
-{
-  return hash_string_lookup (individual_file_table, name);
-}
-
 \f
 
 /* Return the size of the prefix of FILE_NAME that is removed after
 \f
 
 /* Return the size of the prefix of FILE_NAME that is removed after