]> Dogcows Code - chaz/tar/blobdiff - src/incremen.c
Update copyright years.
[chaz/tar] / src / incremen.c
index d75b844c979b0871c47a13757246a171eb806ac4..884d2fa2865831ad3a8ffec73aa617872324fa33 100644 (file)
@@ -1,21 +1,22 @@
 /* GNU dump extensions to tar.
 
-   Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
-   2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+   Copyright 1988, 1992-1994, 1996-1997, 1999-2001, 2003-2009, 2013-2014
+   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
-   Free Software Foundation; either version 3, or (at your option) any later
-   version.
+   This file is part of GNU tar.
 
-   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.
+   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 3 of the License, or
+   (at your option) any later version.
 
-   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.  */
+   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.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <system.h>
 #include <hash.h>
@@ -43,20 +44,29 @@ enum children
 #define DIR_IS_INITED(d) ((d)->flags & DIRF_INIT)
 #define DIR_IS_NFS(d) ((d)->flags & DIRF_NFS)
 #define DIR_IS_FOUND(d) ((d)->flags & DIRF_FOUND)
-#define DIR_IS_NEW(d) ((d)->flags & DIRF_NEW)
+/* #define DIR_IS_NEW(d) ((d)->flags & DIRF_NEW) FIXME: not used */
 #define DIR_IS_RENAMED(d) ((d)->flags & DIRF_RENAMED)
 
 #define DIR_SET_FLAG(d,f) (d)->flags |= (f)
 #define DIR_CLEAR_FLAG(d,f) (d)->flags &= ~(f)
 
+struct dumpdir                 /* Dump directory listing */
+{
+  char *contents;              /* Actual contents */
+  size_t total;                /* Total number of elements */
+  size_t elc;                  /* Number of D/N/Y elements. */
+  char **elv;                  /* Array of D/N/Y elements */
+};
+
 /* Directory attributes.  */
 struct directory
   {
+    struct directory *next;
     struct timespec mtime;      /* Modification time */
     dev_t device_number;       /* device number for directory */
     ino_t inode_number;                /* inode number for directory */
-    char *contents;             /* Directory contents */
-    char *icontents;            /* Initial contents if the directory was
+    struct dumpdir *dump;       /* Directory contents */
+    struct dumpdir *idump;      /* Initial contents if the directory was
                                   rescanned */
     enum children children;     /* What to save under this directory */
     unsigned flags;             /* See DIRF_ macros above */
@@ -64,9 +74,133 @@ struct directory
                                   the original directory structure */
     const char *tagfile;        /* Tag file, if the directory falls under
                                   exclusion_tag_under */
-    char name[1];              /* file name of directory */
+    char *caname;               /* canonical name */
+    char *name;                        /* file name of directory */
   };
 
+static struct dumpdir *
+dumpdir_create0 (const char *contents, const char *cmask)
+{
+  struct dumpdir *dump;
+  size_t i, total, ctsize, len;
+  char *p;
+  const char *q;
+
+  for (i = 0, total = 0, ctsize = 1, q = contents; *q; total++, q += len)
+    {
+      len = strlen (q) + 1;
+      ctsize += len;
+      if (!cmask || strchr (cmask, *q))
+       i++;
+    }
+  dump = xmalloc (sizeof (*dump) + ctsize);
+  dump->contents = (char*)(dump + 1);
+  memcpy (dump->contents, contents, ctsize);
+  dump->total = total;
+  dump->elc = i;
+  dump->elv = xcalloc (i + 1, sizeof (dump->elv[0]));
+
+  for (i = 0, p = dump->contents; *p; p += strlen (p) + 1)
+    {
+      if (!cmask || strchr (cmask, *p))
+       dump->elv[i++] = p + 1;
+    }
+  dump->elv[i] = NULL;
+  return dump;
+}
+
+static struct dumpdir *
+dumpdir_create (const char *contents)
+{
+  return dumpdir_create0 (contents, "YND");
+}
+
+static void
+dumpdir_free (struct dumpdir *dump)
+{
+  free (dump->elv);
+  free (dump);
+}
+
+static int
+compare_dirnames (const void *first, const void *second)
+{
+  char const *const *name1 = first;
+  char const *const *name2 = second;
+  return strcmp (*name1, *name2);
+}
+
+/* Locate NAME in the dumpdir array DUMP.
+   Return pointer to the slot in DUMP->contents, or NULL if not found */
+static char *
+dumpdir_locate (struct dumpdir *dump, const char *name)
+{
+  char **ptr;
+  if (!dump)
+    return NULL;
+
+  ptr = bsearch (&name, dump->elv, dump->elc, sizeof (dump->elv[0]),
+                compare_dirnames);
+  return ptr ? *ptr - 1: NULL;
+}
+
+struct dumpdir_iter
+{
+  struct dumpdir *dump; /* Dumpdir being iterated */
+  int all;              /* Iterate over all entries, not only D/N/Y */
+  size_t next;          /* Index of the next element */
+};
+
+static char *
+dumpdir_next (struct dumpdir_iter *itr)
+{
+  size_t cur = itr->next;
+  char *ret = NULL;
+
+  if (itr->all)
+    {
+      ret = itr->dump->contents + cur;
+      if (*ret == 0)
+       return NULL;
+      itr->next += strlen (ret) + 1;
+    }
+  else if (cur < itr->dump->elc)
+    {
+      ret = itr->dump->elv[cur] - 1;
+      itr->next++;
+    }
+
+  return ret;
+}
+
+static char *
+dumpdir_first (struct dumpdir *dump, int all, struct dumpdir_iter **pitr)
+{
+  struct dumpdir_iter *itr = xmalloc (sizeof (*itr));
+  itr->dump = dump;
+  itr->all = all;
+  itr->next = 0;
+  *pitr = itr;
+  return dumpdir_next (itr);
+}
+
+/* Return size in bytes of the dumpdir array P */
+size_t
+dumpdir_size (const char *p)
+{
+  size_t totsize = 0;
+
+  while (*p)
+    {
+      size_t size = strlen (p) + 1;
+      totsize += size;
+      p += size;
+    }
+  return totsize + 1;
+}
+
+\f
+static struct directory *dirhead, *dirtail;
 static Hash_table *directory_table;
 static Hash_table *directory_meta_table;
 
@@ -80,19 +214,19 @@ static Hash_table *directory_meta_table;
 
 /* Calculate the hash of a directory.  */
 static size_t
-hash_directory_name (void const *entry, size_t n_buckets)
+hash_directory_canonical_name (void const *entry, size_t n_buckets)
 {
   struct directory const *directory = entry;
-  return hash_string (directory->name, n_buckets);
+  return hash_string (directory->caname, n_buckets);
 }
 
 /* Compare two directories for equality of their names. */
 static bool
-compare_directory_names (void const *entry1, void const *entry2)
+compare_directory_canonical_names (void const *entry1, void const *entry2)
 {
   struct directory const *directory1 = entry1;
   struct directory const *directory2 = entry2;
-  return strcmp (directory1->name, directory2->name) == 0;
+  return strcmp (directory1->caname, directory2->caname) == 0;
 }
 
 static size_t
@@ -113,32 +247,88 @@ compare_directory_meta (void const *entry1, void const *entry2)
             && directory1->inode_number == directory2->inode_number;
 }
 
-/* Make a directory entry for given NAME */
+/* Make a directory entry for given relative NAME and canonical name CANAME.
+   The latter is "stolen", i.e. the returned directory contains pointer to
+   it. */
 static struct directory *
-make_directory (const char *name)
+make_directory (const char *name, char *caname)
 {
   size_t namelen = strlen (name);
-  size_t size = offsetof (struct directory, name) + namelen + 1;
-  struct directory *directory = xmalloc (size);
-  directory->contents = directory->icontents = NULL;
+  struct directory *directory = xmalloc (sizeof (*directory));
+  directory->next = NULL;
+  directory->dump = directory->idump = NULL;
   directory->orig = NULL;
   directory->flags = false;
-  strcpy (directory->name, name);
-  if (namelen && ISSLASH (directory->name[namelen - 1]))
-    directory->name[namelen - 1] = 0;
+  if (namelen > 1 && ISSLASH (name[namelen - 1]))
+    namelen--;
+  directory->name = xmalloc (namelen + 1);
+  memcpy (directory->name, name, namelen);
+  directory->name[namelen] = 0;
+  directory->caname = caname;
   directory->tagfile = NULL;
   return directory;
 }
 
+static void
+free_directory (struct directory *dir)
+{
+  free (dir->caname);
+  free (dir->name);
+  free (dir);
+}
+
+static struct directory *
+attach_directory (const char *name)
+{
+  char *cname = normalize_filename (chdir_current, name);
+  struct directory *dir = make_directory (name, cname);
+  if (dirtail)
+    dirtail->next = dir;
+  else
+    dirhead = dir;
+  dirtail = dir;
+  return dir;
+}
+
+\f
+static void
+dirlist_replace_prefix (const char *pref, const char *repl)
+{
+  struct directory *dp;
+  size_t pref_len = strlen (pref);
+  size_t repl_len = strlen (repl);
+  for (dp = dirhead; dp; dp = dp->next)
+    replace_prefix (&dp->name, pref, pref_len, repl, repl_len);
+}
+
+void
+clear_directory_table (void)
+{
+  struct directory *dp;
+
+  if (directory_table)
+    hash_clear (directory_table);
+  if (directory_meta_table)
+    hash_clear (directory_meta_table);
+  for (dp = dirhead; dp; )
+    {
+      struct directory *next = dp->next;
+      free_directory (dp);
+      dp = next;
+    }
+  dirhead = dirtail = NULL;
+}
+
 /* Create and link a new directory entry for directory NAME, having a
    device number DEV and an inode number INO, with NFS indicating
    whether it is an NFS device and FOUND indicating whether we have
    found that the directory exists.  */
 static struct directory *
 note_directory (char const *name, struct timespec mtime,
-               dev_t dev, ino_t ino, bool nfs, bool found, char *contents)
+               dev_t dev, ino_t ino, bool nfs, bool found,
+               const char *contents)
 {
-  struct directory *directory = make_directory (name);
+  struct directory *directory = attach_directory (name);
 
   directory->mtime = mtime;
   directory->device_number = dev;
@@ -149,18 +339,15 @@ note_directory (char const *name, struct timespec mtime,
   if (found)
     DIR_SET_FLAG (directory, DIRF_FOUND);
   if (contents)
-    {
-      size_t size = dumpdir_size (contents);
-      directory->contents = xmalloc (size);
-      memcpy (directory->contents, contents, size);
-    }
+    directory->dump = dumpdir_create (contents);
   else
-    directory->contents = NULL;
+    directory->dump = NULL;
 
   if (! ((directory_table
          || (directory_table = hash_initialize (0, 0,
-                                                hash_directory_name,
-                                                compare_directory_names, 0)))
+                                                hash_directory_canonical_name,
+                                                compare_directory_canonical_names,
+                                                0)))
         && hash_insert (directory_table, directory)))
     xalloc_die ();
 
@@ -183,13 +370,38 @@ find_directory (const char *name)
     return 0;
   else
     {
-      struct directory *dir = make_directory (name);
+      char *caname = normalize_filename (chdir_current, name);
+      struct directory *dir = make_directory (name, caname);
       struct directory *ret = hash_lookup (directory_table, dir);
-      free (dir);
+      free_directory (dir);
       return ret;
     }
 }
 
+#if 0
+/* Remove directory entry for the given CANAME */
+void
+remove_directory (const char *caname)
+{
+  struct directory *dir = make_directory (caname, xstrdup (caname));
+  struct directory *ret = hash_delete (directory_table, dir);
+  if (ret)
+    free_directory (ret);
+  free_directory (dir);
+}
+#endif
+
+/* If first OLD_PREFIX_LEN bytes of DIR->NAME name match OLD_PREFIX,
+   replace them with NEW_PREFIX. */
+void
+rebase_directory (struct directory *dir,
+                 const char *old_prefix, size_t old_prefix_len,
+                 const char *new_prefix, size_t new_prefix_len)
+{
+  replace_prefix (&dir->name, old_prefix, old_prefix_len,
+                 new_prefix, new_prefix_len);
+}
+
 /* Return a directory entry for a given combination of device and inode
    numbers, or zero if none found.  */
 static struct directory *
@@ -199,49 +411,64 @@ find_directory_meta (dev_t dev, ino_t ino)
     return 0;
   else
     {
-      struct directory *dir = make_directory ("");
+      struct directory *dir = make_directory ("", NULL);
       struct directory *ret;
       dir->device_number = dev;
       dir->inode_number = ino;
       ret = hash_lookup (directory_meta_table, dir);
-      free (dir);
+      free_directory (dir);
       return ret;
     }
 }
 
 void
-update_parent_directory (const char *name)
+update_parent_directory (struct tar_stat_info *parent)
 {
-  struct directory *directory;
-  char *p;
-
-  p = dir_name (name);
-  directory = find_directory (p);
+  struct directory *directory = find_directory (parent->orig_file_name);
   if (directory)
     {
       struct stat st;
-      if (deref_stat (dereference_option, p, &st) != 0)
-       stat_diag (name);
+      if (fstat (parent->fd, &st) != 0)
+       stat_diag (directory->name);
       else
        directory->mtime = get_stat_mtime (&st);
     }
-  free (p);
 }
 
+#define PD_FORCE_CHILDREN 0x10
+#define PD_FORCE_INIT     0x20
+#define PD_CHILDREN(f) ((f) & 3)
+
 static struct directory *
-procdir (char *name_buffer, struct stat *stat_data,
-        dev_t device,
-        enum children children,
-        bool verbose,
+procdir (const char *name_buffer, struct tar_stat_info *st,
+        int flag,
         char *entry)
 {
   struct directory *directory;
+  struct stat *stat_data = &st->stat;
   bool nfs = NFS_FILE_STAT (*stat_data);
+  bool perhaps_renamed = false;
 
   if ((directory = find_directory (name_buffer)) != NULL)
     {
       if (DIR_IS_INITED (directory))
-       return directory;
+       {
+         if (flag & PD_FORCE_INIT)
+           {
+             assign_string (&directory->name, name_buffer);
+           }
+         else
+           {
+             *entry = 'N'; /* Avoid duplicating this directory */
+             return directory;
+           }
+       }
+
+      if (strcmp (directory->name, name_buffer))
+       {
+         *entry = 'N';
+         return directory;
+       }
 
       /* With NFS, the same file can have two different devices
         if an NFS directory is mounted in multiple locations,
@@ -260,19 +487,22 @@ procdir (char *name_buffer, struct stat *stat_data,
                                                     stat_data->st_ino);
          if (d)
            {
-             if (verbose_option)
-               WARN ((0, 0, _("%s: Directory has been renamed from %s"),
-                      quotearg_colon (name_buffer),
-                      quote_n (1, d->name)));
-             directory->orig = d;
-             DIR_SET_FLAG (directory, DIRF_RENAMED);
+             if (strcmp (d->name, name_buffer))
+               {
+                 WARNOPT (WARN_RENAME_DIRECTORY,
+                          (0, 0,
+                           _("%s: Directory has been renamed from %s"),
+                           quotearg_colon (name_buffer),
+                           quote_n (1, d->name)));
+                 directory->orig = d;
+                 DIR_SET_FLAG (directory, DIRF_RENAMED);
+                 dirlist_replace_prefix (d->name, name_buffer);
+               }
              directory->children = CHANGED_CHILDREN;
            }
          else
            {
-             if (verbose_option)
-               WARN ((0, 0, _("%s: Directory has been renamed"),
-                      quotearg_colon (name_buffer)));
+             perhaps_renamed = true;
              directory->children = ALL_CHILDREN;
              directory->device_number = stat_data->st_dev;
              directory->inode_number = stat_data->st_ino;
@@ -291,7 +521,7 @@ procdir (char *name_buffer, struct stat *stat_data,
                                                 stat_data->st_ino);
 
       directory = note_directory (name_buffer,
-                                 get_stat_mtime(stat_data),
+                                 get_stat_mtime (stat_data),
                                  stat_data->st_dev,
                                  stat_data->st_ino,
                                  nfs,
@@ -300,20 +530,24 @@ procdir (char *name_buffer, struct stat *stat_data,
 
       if (d)
        {
-         if (verbose)
-           WARN ((0, 0, _("%s: Directory has been renamed from %s"),
-                  quotearg_colon (name_buffer),
-                  quote_n (1, d->name)));
-         directory->orig = d;
-         DIR_SET_FLAG (directory, DIRF_RENAMED);
+         if (strcmp (d->name, name_buffer))
+           {
+             WARNOPT (WARN_RENAME_DIRECTORY,
+                      (0, 0, _("%s: Directory has been renamed from %s"),
+                       quotearg_colon (name_buffer),
+                       quote_n (1, d->name)));
+             directory->orig = d;
+             DIR_SET_FLAG (directory, DIRF_RENAMED);
+             dirlist_replace_prefix (d->name, name_buffer);
+           }
          directory->children = CHANGED_CHILDREN;
        }
       else
        {
          DIR_SET_FLAG (directory, DIRF_NEW);
-         if (verbose)
-           WARN ((0, 0, _("%s: Directory is new"),
-                  quotearg_colon (name_buffer)));
+         WARNOPT (WARN_NEW_DIRECTORY,
+                  (0, 0, _("%s: Directory is new"),
+                   quotearg_colon (name_buffer)));
          directory->children =
            (listed_incremental_option
             || (OLDER_STAT_TIME (*stat_data, m)
@@ -324,100 +558,72 @@ procdir (char *name_buffer, struct stat *stat_data,
        }
     }
 
-  /* If the directory is on another device and --one-file-system was given,
-     omit it... */
-  if (one_file_system_option && device != stat_data->st_dev
-      /* ... except if it was explicitely given in the command line */
-      && !is_individual_file (name_buffer))
-    directory->children = NO_CHILDREN;
-  else if (children == ALL_CHILDREN)
-    directory->children = ALL_CHILDREN;
-
-  DIR_SET_FLAG (directory, DIRF_INIT);
+  if (one_file_system_option && st->parent
+      && stat_data->st_dev != st->parent->stat.st_dev)
+    {
+      WARNOPT (WARN_XDEV,
+              (0, 0,
+               _("%s: directory is on a different filesystem; not dumped"),
+               quotearg_colon (directory->name)));
+      directory->children = NO_CHILDREN;
+      /* If there is any dumpdir info in that directory, remove it */
+      if (directory->dump)
+       {
+         dumpdir_free (directory->dump);
+         directory->dump = NULL;
+       }
+      perhaps_renamed = false;
+    }
 
-  {
-    const char *tag_file_name;
-
-    switch (check_exclusion_tags (name_buffer, &tag_file_name))
-      {
-      case exclusion_tag_all:
-       /* This warning can be duplicated by code in dump_file0, but only
-          in case when the topmost directory being archived contains
-          an exclusion tag. */
-       exclusion_tag_warning (name_buffer, tag_file_name,
-                              _("directory not dumped"));
-       if (entry)
-         *entry = 'N';
-       directory->children = NO_CHILDREN;
-       break;
+  else if (flag & PD_FORCE_CHILDREN)
+    {
+      directory->children = PD_CHILDREN(flag);
+      if (directory->children == NO_CHILDREN)
+       *entry = 'N';
+    }
 
-      case exclusion_tag_contents:
-       exclusion_tag_warning (name_buffer, tag_file_name,
-                              _("contents not dumped"));
-       directory->children = NO_CHILDREN;
-       break;
+  if (perhaps_renamed)
+    WARNOPT (WARN_RENAME_DIRECTORY,
+            (0, 0, _("%s: Directory has been renamed"),
+             quotearg_colon (name_buffer)));
 
-      case exclusion_tag_under:
-       exclusion_tag_warning (name_buffer, tag_file_name,
-                              _("contents not dumped"));
-       directory->tagfile = tag_file_name;
-       break;
+  DIR_SET_FLAG (directory, DIRF_INIT);
 
-      case exclusion_tag_none:
-       break;
-      }
-  }
+  if (directory->children != NO_CHILDREN)
+    {
+      const char *tag_file_name;
 
-  return directory;
-}
+      switch (check_exclusion_tags (st, &tag_file_name))
+       {
+       case exclusion_tag_all:
+         /* This warning can be duplicated by code in dump_file0, but only
+            in case when the topmost directory being archived contains
+            an exclusion tag. */
+         exclusion_tag_warning (name_buffer, tag_file_name,
+                                _("directory not dumped"));
+         *entry = 'N';
+         directory->children = NO_CHILDREN;
+         break;
 
-/* Locate NAME in the dumpdir array DUMP.
-   Return pointer to the slot in the array, or NULL if not found */
-const char *
-dumpdir_locate (const char *dump, const char *name)
-{
-  if (dump)
-    while (*dump)
-      {
-       /* Ignore 'R' (rename) and 'X' (tempname) entries, since they break
-          alphabetical ordering.
-          They normally do not occur in dumpdirs from the snapshot files,
-          but this function is also used by purge_directory, which operates
-          on a dumpdir from the archive, hence the need for this test. */
-       if (!strchr ("RX", *dump))
-         {
-           int rc = strcmp (dump + 1, name);
-           if (rc == 0)
-             return dump;
-           if (rc > 1)
-             break;
-         }
-       dump += strlen (dump) + 1;
-      }
-  return NULL;
-}
+       case exclusion_tag_contents:
+         exclusion_tag_warning (name_buffer, tag_file_name,
+                                _("contents not dumped"));
+         directory->children = NO_CHILDREN;
+         directory->tagfile = tag_file_name;
+         break;
 
-/* Return size in bytes of the dumpdir array P */
-size_t
-dumpdir_size (const char *p)
-{
-  size_t totsize = 0;
+       case exclusion_tag_under:
+         exclusion_tag_warning (name_buffer, tag_file_name,
+                                _("contents not dumped"));
+         directory->tagfile = tag_file_name;
+         break;
 
-  while (*p)
-    {
-      size_t size = strlen (p) + 1;
-      totsize += size;
-      p += size;
+       case exclusion_tag_none:
+         break;
+       }
     }
-  return totsize + 1;
-}
 
-static int
-compare_dirnames (const void *first, const void *second)
-{
-  char const *const *name1 = first;
-  char const *const *name2 = second;
-  return strcmp (*name1, *name2);
+  return directory;
 }
 
 /* Compare dumpdir array from DIRECTORY with directory listing DIR and
@@ -425,13 +631,13 @@ compare_dirnames (const void *first, const void *second)
 
    DIR must be returned by a previous call to savedir().
 
-   File names in DIRECTORY->contents must be sorted
+   File names in DIRECTORY->dump->contents must be sorted
    alphabetically.
 
-   DIRECTORY->contents is replaced with the created template. Each entry is
+   DIRECTORY->dump is replaced with the created template. Each entry is
    prefixed with ' ' if it was present in DUMP and with 'Y' otherwise. */
 
-void
+static void
 makedumpdir (struct directory *directory, const char *dir)
 {
   size_t i,
@@ -440,15 +646,15 @@ makedumpdir (struct directory *directory, const char *dir)
   const char *p;
   char const **array;
   char *new_dump, *new_dump_ptr;
-  const char *dump;
+  struct dumpdir *dump;
 
   if (directory->children == ALL_CHILDREN)
     dump = NULL;
   else if (DIR_IS_RENAMED (directory))
-    dump = directory->orig->icontents ?
-              directory->orig->icontents : directory->orig->contents;
+    dump = directory->orig->idump ?
+           directory->orig->idump : directory->orig->dump;
   else
-    dump = directory->contents;
+    dump = directory->dump;
 
   /* Count the size of DIR and the number of elements it contains */
   dirsize = 0;
@@ -475,16 +681,13 @@ makedumpdir (struct directory *directory, const char *dir)
       if (loc)
        {
          if (directory->tagfile)
-           *new_dump_ptr = strcmp (directory->tagfile, array[i]) == 0 ?
-                              ' ' : 'I';
+           *new_dump_ptr = 'I';
          else
            *new_dump_ptr = ' ';
          new_dump_ptr++;
-         dump = loc + strlen (loc) + 1;
        }
       else if (directory->tagfile)
-       *new_dump_ptr++ = strcmp (directory->tagfile, array[i]) == 0 ?
-                              ' ' : 'I';
+       *new_dump_ptr++ = 'I';
       else
        *new_dump_ptr++ = 'Y'; /* New entry */
 
@@ -493,123 +696,172 @@ makedumpdir (struct directory *directory, const char *dir)
        ;
     }
   *new_dump_ptr = 0;
-  directory->icontents = directory->contents;
-  directory->contents = new_dump;
+  directory->idump = directory->dump;
+  directory->dump = dumpdir_create0 (new_dump, NULL);
+  free (new_dump);
   free (array);
 }
 
-/* Recursively scan the given directory. */
-static char *
-scan_directory (char *dir, dev_t device)
+/* Create a dumpdir containing only one entry: that for the
+   tagfile. */
+static void
+maketagdumpdir (struct directory *directory)
 {
-  char *dirp = savedir (dir);  /* for scanning directory */
-  char *name_buffer;           /* directory, `/', and directory member */
-  size_t name_buffer_size;     /* allocated size of name_buffer, minus 2 */
-  size_t name_length;          /* used length in name_buffer */
-  struct stat stat_data;
+  size_t len = strlen (directory->tagfile) + 1;
+  char *new_dump = xmalloc (len + 2);
+  new_dump[0] = 'Y';
+  memcpy (new_dump + 1, directory->tagfile, len);
+  new_dump[len + 1] = 0;
+
+  directory->idump = directory->dump;
+  directory->dump = dumpdir_create0 (new_dump, NULL);
+  free (new_dump);
+}
+
+/* Recursively scan the directory identified by ST.  */
+struct directory *
+scan_directory (struct tar_stat_info *st)
+{
+  char const *dir = st->orig_file_name;
+  char *dirp = get_directory_entries (st);
+  dev_t device = st->stat.st_dev;
+  bool cmdline = ! st->parent;
+  namebuf_t nbuf;
+  char *tmp;
   struct directory *directory;
+  char ch;
 
   if (! dirp)
     savedir_error (dir);
 
-  name_buffer_size = strlen (dir) + NAME_FIELD_SIZE;
-  name_buffer = xmalloc (name_buffer_size + 2);
-  strcpy (name_buffer, dir);
-  if (! ISSLASH (dir[strlen (dir) - 1]))
-    strcat (name_buffer, "/");
-  name_length = strlen (name_buffer);
-
-  if (deref_stat (dereference_option, name_buffer, &stat_data))
-    {
-      stat_diag (name_buffer);
-      /* FIXME: used to be
-           children = CHANGED_CHILDREN;
-        but changed to: */
-      free (name_buffer);
-      free (dirp);
-      return NULL;
-    }
+  tmp = xstrdup (dir);
+  zap_slashes (tmp);
 
-  directory = procdir (name_buffer, &stat_data, device, NO_CHILDREN, false,
-                      NULL);
+  directory = procdir (tmp, st,
+                      (cmdline ? PD_FORCE_INIT : 0),
+                      &ch);
 
-  if (dirp && directory->children != NO_CHILDREN)
-    {
-      char  *entry;    /* directory entry being scanned */
-      size_t entrylen; /* length of directory entry */
+  free (tmp);
 
-      makedumpdir (directory, dirp);
+  nbuf = namebuf_create (dir);
 
-      for (entry = directory->contents;
-          (entrylen = strlen (entry)) != 0;
-          entry += entrylen + 1)
+  if (dirp)
+    {
+      if (directory->children != NO_CHILDREN)
        {
-         if (name_buffer_size <= entrylen - 1 + name_length)
-           {
-             do
-               name_buffer_size += NAME_FIELD_SIZE;
-             while (name_buffer_size <= entrylen - 1 + name_length);
-             name_buffer = xrealloc (name_buffer, name_buffer_size + 2);
-           }
-         strcpy (name_buffer + name_length, entry + 1);
+         char *entry;  /* directory entry being scanned */
+         struct dumpdir_iter *itr;
 
-         if (*entry == 'I') /* Ignored entry */
-           *entry = 'N';
-         else if (excluded_name (name_buffer))
-           *entry = 'N';
-         else
-           {
-             if (deref_stat (dereference_option, name_buffer, &stat_data))
-               {
-                 stat_diag (name_buffer);
-                 *entry = 'N';
-                 continue;
-               }
+         makedumpdir (directory, dirp);
 
-             if (S_ISDIR (stat_data.st_mode))
-               {
-                 *entry = 'D';
-                 procdir (name_buffer, &stat_data, device,
-                          directory->children,
-                          verbose_option, entry);
-               }
+         for (entry = dumpdir_first (directory->dump, 1, &itr);
+              entry;
+              entry = dumpdir_next (itr))
+           {
+             char *full_name = namebuf_name (nbuf, entry + 1);
 
-             else if (one_file_system_option && device != stat_data.st_dev)
+             if (*entry == 'I') /* Ignored entry */
                *entry = 'N';
-
-             else if (*entry == 'Y')
-               /* New entry, skip further checks */;
-
-             /* FIXME: if (S_ISHIDDEN (stat_data.st_mode))?? */
-
-             else if (OLDER_STAT_TIME (stat_data, m)
-                      && (!after_date_option
-                          || OLDER_STAT_TIME (stat_data, c)))
+             else if (excluded_name (full_name))
                *entry = 'N';
              else
-               *entry = 'Y';
+               {
+                 int fd = st->fd;
+                 void (*diag) (char const *) = 0;
+                 struct tar_stat_info stsub;
+                 tar_stat_init (&stsub);
+
+                 if (fd < 0)
+                   {
+                     errno = - fd;
+                     diag = open_diag;
+                   }
+                 else if (fstatat (fd, entry + 1, &stsub.stat,
+                                   fstatat_flags) != 0)
+                   diag = stat_diag;
+                 else if (S_ISDIR (stsub.stat.st_mode))
+                   {
+                     int subfd = subfile_open (st, entry + 1,
+                                               open_read_flags);
+                     if (subfd < 0)
+                       diag = open_diag;
+                     else
+                       {
+                         stsub.fd = subfd;
+                         if (fstat (subfd, &stsub.stat) != 0)
+                           diag = stat_diag;
+                       }
+                   }
+
+                 if (diag)
+                   {
+                     file_removed_diag (full_name, false, diag);
+                     *entry = 'N';
+                   }
+                 else if (S_ISDIR (stsub.stat.st_mode))
+                   {
+                     int pd_flag = 0;
+                     if (!recursion_option)
+                       pd_flag |= PD_FORCE_CHILDREN | NO_CHILDREN;
+                     else if (directory->children == ALL_CHILDREN)
+                       pd_flag |= PD_FORCE_CHILDREN | ALL_CHILDREN;
+                     *entry = 'D';
+
+                     stsub.parent = st;
+                     procdir (full_name, &stsub, pd_flag, entry);
+                     restore_parent_fd (&stsub);
+                   }
+                 else if (one_file_system_option &&
+                          device != stsub.stat.st_dev)
+                   *entry = 'N';
+                 else if (*entry == 'Y')
+                   /* New entry, skip further checks */;
+                 /* FIXME: if (S_ISHIDDEN (stat_data.st_mode))?? */
+                 else if (OLDER_STAT_TIME (stsub.stat, m)
+                          && (!after_date_option
+                              || OLDER_STAT_TIME (stsub.stat, c)))
+                   *entry = 'N';
+                 else
+                   *entry = 'Y';
+
+                 tar_stat_destroy (&stsub);
+               }
            }
+         free (itr);
        }
+      else if (directory->tagfile)
+       maketagdumpdir (directory);
     }
 
-  free (name_buffer);
-  if (dirp)
-    free (dirp);
+  namebuf_free (nbuf);
 
-  return directory->contents;
+  free (dirp);
+
+  return directory;
+}
+
+/* Return pointer to the contents of the directory DIR */
+const char *
+directory_contents (struct directory *dir)
+{
+  if (!dir)
+    return NULL;
+  return dir->dump ? dir->dump->contents : NULL;
 }
 
-char *
-get_directory_contents (char *dir, dev_t device)
+/* A "safe" version of directory_contents, which never returns NULL. */
+const char *
+safe_directory_contents (struct directory *dir)
 {
-  return scan_directory (dir, device);
+  const char *ret = directory_contents (dir);
+  return ret ? ret : "\0\0\0\0";
 }
 
 \f
 static void
-obstack_code_rename (struct obstack *stk, char *from, char *to)
+obstack_code_rename (struct obstack *stk, char const *from, char const *to)
 {
-  char *s;
+  char const *s;
 
   s = from[0] == 0 ? from :
                      safer_name_suffix (from, false, absolute_names_option);
@@ -622,12 +874,9 @@ obstack_code_rename (struct obstack *stk, char *from, char *to)
   obstack_grow (stk, s, strlen (s) + 1);
 }
 
-static bool
-rename_handler (void *data, void *proc_data)
+static void
+store_rename (struct directory *dir, struct obstack *stk)
 {
-  struct directory *dir = data;
-  struct obstack *stk = proc_data;
-
   if (DIR_IS_RENAMED (dir))
     {
       struct directory *prev, *p;
@@ -636,7 +885,7 @@ rename_handler (void *data, void *proc_data)
         are ignored when hit by this function next time.
         If the chain forms a cycle, prev points to the entry DIR is renamed
         from. In this case it still retains DIRF_RENAMED flag, which will be
-        cleared in the `else' branch below */
+        cleared in the 'else' branch below */
       for (prev = dir; prev && prev->orig != dir; prev = prev->orig)
        DIR_CLEAR_FLAG (prev, DIRF_RENAMED);
 
@@ -666,19 +915,21 @@ rename_handler (void *data, void *proc_data)
          obstack_code_rename (stk, "", prev->name);
        }
     }
-  return true;
 }
 
-const char *
-append_incremental_renames (const char *dump)
+void
+append_incremental_renames (struct directory *dir)
 {
   struct obstack stk;
   size_t size;
+  struct directory *dp;
+  const char *dump;
 
-  if (directory_table == NULL)
-    return dump;
+  if (dirhead == NULL)
+    return;
 
   obstack_init (&stk);
+  dump = directory_contents (dir);
   if (dump)
     {
       size = dumpdir_size (dump) - 1;
@@ -687,15 +938,17 @@ append_incremental_renames (const char *dump)
   else
     size = 0;
 
-  hash_do_for_each (directory_table, rename_handler, &stk);
-  if (obstack_object_size (&stk) != size)
+  for (dp = dirhead; dp; dp = dp->next)
+    store_rename (dp, &stk);
+
+  /* FIXME: Is this the right thing to do when DIR is null?  */
+  if (dir && obstack_object_size (&stk) != size)
     {
       obstack_1grow (&stk, 0);
-      dump = obstack_finish (&stk);
+      dumpdir_free (dir->dump);
+      dir->dump = dumpdir_create (obstack_finish (&stk));
     }
-  else
-    obstack_free (&stk, NULL);
-  return dump;
+  obstack_free (&stk, NULL);
 }
 
 \f
@@ -718,10 +971,8 @@ read_incr_db_01 (int version, const char *initbuf)
 {
   int n;
   uintmax_t u;
-  time_t sec;
-  long int nsec;
-  char *buf = 0;
-  size_t bufsize;
+  char *buf = NULL;
+  size_t bufsize = 0;
   char *ebuf;
   long lineno = 1;
 
@@ -741,21 +992,15 @@ read_incr_db_01 (int version, const char *initbuf)
       bufsize = strlen (buf) + 1;
     }
 
-  sec = TYPE_MINIMUM (time_t);
-  nsec = -1;
-  errno = 0;
-  u = strtoumax (buf, &ebuf, 10);
-  if (!errno && TYPE_MAXIMUM (time_t) < u)
-    errno = ERANGE;
-  if (errno || buf == ebuf)
+  newer_mtime_option = decode_timespec (buf, &ebuf, false);
+
+  if (! valid_timespec (newer_mtime_option))
     ERROR ((0, errno, "%s:%ld: %s",
            quotearg_colon (listed_incremental_option),
            lineno,
            _("Invalid time stamp")));
   else
     {
-      sec = u;
-
       if (version == 1 && *ebuf)
        {
          char const *buf_ns = ebuf + 1;
@@ -769,20 +1014,13 @@ read_incr_db_01 (int version, const char *initbuf)
                      quotearg_colon (listed_incremental_option),
                      lineno,
                      _("Invalid time stamp")));
-             sec = TYPE_MINIMUM (time_t);
+             newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+             newer_mtime_option.tv_nsec = -1;
            }
          else
-           nsec = u;
-       }
-      else
-       {
-         /* pre-1 incremental format does not contain nanoseconds */
-         nsec = 0;
+           newer_mtime_option.tv_nsec = u;
        }
     }
-  newer_mtime_option.tv_sec = sec;
-  newer_mtime_option.tv_nsec = nsec;
-
 
   while (0 < (n = getline (&buf, &bufsize, listed_incremental_stream)))
     {
@@ -799,20 +1037,12 @@ read_incr_db_01 (int version, const char *initbuf)
 
       if (version == 1)
        {
-         errno = 0;
-         u = strtoumax (strp, &ebuf, 10);
-         if (!errno && TYPE_MAXIMUM (time_t) < u)
-           errno = ERANGE;
-         if (errno || strp == ebuf || *ebuf != ' ')
-           {
-             ERROR ((0, errno, "%s:%ld: %s",
-                     quotearg_colon (listed_incremental_option), lineno,
-                     _("Invalid modification time (seconds)")));
-             sec = (time_t) -1;
-           }
-         else
-           sec = u;
+         mtime = decode_timespec (strp, &ebuf, false);
          strp = ebuf;
+         if (!valid_timespec (mtime) || *strp != ' ')
+           ERROR ((0, errno, "%s:%ld: %s",
+                   quotearg_colon (listed_incremental_option), lineno,
+                   _("Invalid modification time")));
 
          errno = 0;
          u = strtoumax (strp, &ebuf, 10);
@@ -823,46 +1053,30 @@ read_incr_db_01 (int version, const char *initbuf)
              ERROR ((0, errno, "%s:%ld: %s",
                      quotearg_colon (listed_incremental_option), lineno,
                      _("Invalid modification time (nanoseconds)")));
-             nsec = -1;
+             mtime.tv_nsec = -1;
            }
          else
-           nsec = u;
-         mtime.tv_sec = sec;
-         mtime.tv_nsec = nsec;
+           mtime.tv_nsec = u;
          strp = ebuf;
        }
       else
-       memset (&mtime, 0, sizeof mtime);
+       mtime.tv_sec = mtime.tv_nsec = 0;
 
-      errno = 0;
-      u = strtoumax (strp, &ebuf, 10);
-      if (!errno && TYPE_MAXIMUM (dev_t) < u)
-       errno = ERANGE;
-      if (errno || strp == ebuf || *ebuf != ' ')
-       {
-         ERROR ((0, errno, "%s:%ld: %s",
-                 quotearg_colon (listed_incremental_option), lineno,
-                 _("Invalid device number")));
-         dev = (dev_t) -1;
-       }
-      else
-       dev = u;
+      dev = strtosysint (strp, &ebuf,
+                        TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t));
       strp = ebuf;
+      if (errno || *strp != ' ')
+       ERROR ((0, errno, "%s:%ld: %s",
+               quotearg_colon (listed_incremental_option), lineno,
+               _("Invalid device number")));
 
-      errno = 0;
-      u = strtoumax (strp, &ebuf, 10);
-      if (!errno && TYPE_MAXIMUM (ino_t) < u)
-       errno = ERANGE;
-      if (errno || strp == ebuf || *ebuf != ' ')
-       {
-         ERROR ((0, errno, "%s:%ld: %s",
-                 quotearg_colon (listed_incremental_option), lineno,
-                 _("Invalid inode number")));
-         ino = (ino_t) -1;
-       }
-      else
-       ino = u;
+      ino = strtosysint (strp, &ebuf,
+                        TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t));
       strp = ebuf;
+      if (errno || *strp != ' ')
+       ERROR ((0, errno, "%s:%ld: %s",
+               quotearg_colon (listed_incremental_option), lineno,
+               _("Invalid inode number")));
 
       strp++;
       unquote_string (strp);
@@ -889,97 +1103,84 @@ read_obstack (FILE *fp, struct obstack *stk, size_t *pcount)
   return c;
 }
 
-/* Read from file FP a nul-terminated string and convert it to
-   intmax_t.  Return the resulting value in PVAL.  Assume '-' has
-   already been read.
+/* Read from file FP a null-terminated string and convert it to an
+   integer.  FIELDNAME is the intended use of the integer, useful for
+   diagnostics.  MIN_VAL and MAX_VAL are its minimum and maximum
+   permissible values; MIN_VAL must be nonpositive and MAX_VAL positive.
+   Store into *PVAL the resulting value, converted to intmax_t.
 
    Throw a fatal error if the string cannot be converted or if the
-   converted value is less than MIN_VAL.  */
+   converted value is out of range.
 
-static void
-read_negative_num (FILE *fp, intmax_t min_val, intmax_t *pval)
+   Return true if successful, false if end of file.  */
+
+static bool
+read_num (FILE *fp, char const *fieldname,
+         intmax_t min_val, uintmax_t max_val, intmax_t *pval)
 {
-  int c;
-  size_t i;
+  int i;
   char buf[INT_BUFSIZE_BOUND (intmax_t)];
-  char *ep;
-  buf[0] = '-';
+  char offbuf[INT_BUFSIZE_BOUND (off_t)];
+  char minbuf[INT_BUFSIZE_BOUND (intmax_t)];
+  char maxbuf[INT_BUFSIZE_BOUND (intmax_t)];
+  int conversion_errno;
+  int c = getc (fp);
+  bool negative = c == '-';
 
-  for (i = 1; ISDIGIT (c = getc (fp)); i++)
+  for (i = 0; (i == 0 && negative) || ISDIGIT (c); i++)
     {
-      if (i == sizeof buf - 1)
-       FATAL_ERROR ((0, 0, _("Field too long while reading snapshot file")));
       buf[i] = c;
-    }
-
-  if (c < 0)
-    {
-      if (ferror (fp))
-       FATAL_ERROR ((0, errno, _("Read error in snapshot file")));
-      else
-       FATAL_ERROR ((0, 0, _("Unexpected EOF in snapshot file")));
-    }
-
-  buf[i] = 0;
-  errno = 0;
-  *pval = strtoimax (buf, &ep, 10);
-  if (c || errno || *pval < min_val)
-    FATAL_ERROR ((0, errno, _("Unexpected field value in snapshot file")));
-}
-
-/* Read from file FP a nul-terminated string and convert it to
-   uintmax_t.  Return the resulting value in PVAL.  Assume C has
-   already been read.
-
-   Throw a fatal error if the string cannot be converted or if the
-   converted value exceeds MAX_VAL.
-
-   Return the last character read or EOF on end of file. */
-
-static int
-read_unsigned_num (int c, FILE *fp, uintmax_t max_val, uintmax_t *pval)
-{
-  size_t i;
-  char buf[UINTMAX_STRSIZE_BOUND], *ep;
-
-  for (i = 0; ISDIGIT (c); i++)
-    {
       if (i == sizeof buf - 1)
-       FATAL_ERROR ((0, 0, _("Field too long while reading snapshot file")));
-      buf[i] = c;
+       FATAL_ERROR ((0, 0,
+                     _("%s: byte %s: %s %.*s... too long"),
+                     quotearg_colon (listed_incremental_option),
+                     offtostr (ftello (fp), offbuf),
+                     fieldname, i + 1, buf));
       c = getc (fp);
     }
 
+  buf[i] = 0;
+
   if (c < 0)
     {
       if (ferror (fp))
-       FATAL_ERROR ((0, errno, _("Read error in snapshot file")));
-      else if (i == 0)
-       return c;
-      else
-       FATAL_ERROR ((0, 0, _("Unexpected EOF in snapshot file")));
+       read_fatal (listed_incremental_option);
+      if (i != 0)
+       FATAL_ERROR ((0, 0, "%s: %s",
+                     quotearg_colon (listed_incremental_option),
+                     _("Unexpected EOF in snapshot file")));
+      return false;
     }
 
-  buf[i] = 0;
-  errno = 0;
-  *pval = strtoumax (buf, &ep, 10);
-  if (c || errno || max_val < *pval)
-    FATAL_ERROR ((0, errno, _("Unexpected field value in snapshot file")));
-  return c;
-}
-
-/* Read from file FP a nul-terminated string and convert it to
-   uintmax_t.  Return the resulting value in PVAL.
+  if (c)
+    FATAL_ERROR ((0, 0,
+                 _("%s: byte %s: %s %s followed by invalid byte 0x%02x"),
+                 quotearg_colon (listed_incremental_option),
+                 offtostr (ftello (fp), offbuf),
+                 fieldname, buf, c));
 
-   Throw a fatal error if the string cannot be converted or if the
-   converted value exceeds MAX_VAL.
+  *pval = strtosysint (buf, NULL, min_val, max_val);
+  conversion_errno = errno;
 
-   Return the last character read or EOF on end of file. */
+  switch (conversion_errno)
+    {
+    case ERANGE:
+      FATAL_ERROR ((0, conversion_errno,
+                   _("%s: byte %s: (valid range %s..%s)\n\t%s %s"),
+                   quotearg_colon (listed_incremental_option),
+                   offtostr (ftello (fp), offbuf),
+                   imaxtostr (min_val, minbuf),
+                   umaxtostr (max_val, maxbuf), fieldname, buf));
+    default:
+      FATAL_ERROR ((0, conversion_errno,
+                   _("%s: byte %s: %s %s"),
+                   quotearg_colon (listed_incremental_option),
+                   offtostr (ftello (fp), offbuf), fieldname, buf));
+    case 0:
+      break;
+    }
 
-static int
-read_num (FILE *fp, uintmax_t max_val, uintmax_t *pval)
-{
-  return read_unsigned_num (getc (fp), fp, max_val, pval);
+  return true;
 }
 
 /* Read from FP two NUL-terminated strings representing a struct
@@ -990,35 +1191,28 @@ read_num (FILE *fp, uintmax_t max_val, uintmax_t *pval)
 static void
 read_timespec (FILE *fp, struct timespec *pval)
 {
-  int c = getc (fp);
-  intmax_t i;
-  uintmax_t u;
+  intmax_t s, ns;
 
-  if (c == '-')
+  if (read_num (fp, "sec", TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t), &s)
+      && read_num (fp, "nsec", 0, BILLION - 1, &ns))
     {
-      read_negative_num (fp, TYPE_MINIMUM (time_t), &i);
-      c = 0;
-      pval->tv_sec = i;
+      pval->tv_sec = s;
+      pval->tv_nsec = ns;
     }
   else
     {
-      c = read_unsigned_num (c, fp, TYPE_MAXIMUM (time_t), &u);
-      pval->tv_sec = u;
+      FATAL_ERROR ((0, 0, "%s: %s",
+                   quotearg_colon (listed_incremental_option),
+                   _("Unexpected EOF in snapshot file")));
     }
-
-  if (c || read_num (fp, BILLION - 1, &u))
-    FATAL_ERROR ((0, 0, "%s: %s",
-                 quotearg_colon (listed_incremental_option),
-                 _("Unexpected EOF in snapshot file")));
-  pval->tv_nsec = u;
 }
 
 /* Read incremental snapshot format 2 */
 static void
-read_incr_db_2 ()
+read_incr_db_2 (void)
 {
-  uintmax_t u;
   struct obstack stk;
+  char offbuf[INT_BUFSIZE_BOUND (off_t)];
 
   obstack_init (&stk);
 
@@ -1026,6 +1220,7 @@ read_incr_db_2 ()
 
   for (;;)
     {
+      intmax_t i;
       struct timespec mtime;
       dev_t dev;
       ino_t ino;
@@ -1034,20 +1229,22 @@ read_incr_db_2 ()
       char *content;
       size_t s;
 
-      if (read_num (listed_incremental_stream, 1, &u))
+      if (! read_num (listed_incremental_stream, "nfs", 0, 1, &i))
        return; /* Normal return */
 
-      nfs = u;
+      nfs = i;
 
       read_timespec (listed_incremental_stream, &mtime);
 
-      if (read_num (listed_incremental_stream, TYPE_MAXIMUM (dev_t), &u))
+      if (! read_num (listed_incremental_stream, "dev",
+                     TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t), &i))
        break;
-      dev = u;
+      dev = i;
 
-      if (read_num (listed_incremental_stream, TYPE_MAXIMUM (ino_t), &u))
+      if (! read_num (listed_incremental_stream, "ino",
+                     TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t), &i))
        break;
-      ino = u;
+      ino = i;
 
       if (read_obstack (listed_incremental_stream, &stk, &s))
        break;
@@ -1057,8 +1254,9 @@ read_incr_db_2 ()
       while (read_obstack (listed_incremental_stream, &stk, &s) == 0 && s > 1)
        ;
       if (getc (listed_incremental_stream) != 0)
-       FATAL_ERROR ((0, 0, "%s: %s",
+       FATAL_ERROR ((0, 0, _("%s: byte %s: %s"),
                      quotearg_colon (listed_incremental_option),
+                     offtostr (ftello (listed_incremental_stream), offbuf),
                      _("Missing record terminator")));
 
       content = obstack_finish (&stk);
@@ -1070,6 +1268,51 @@ read_incr_db_2 ()
                _("Unexpected EOF in snapshot file")));
 }
 
+/* Display (to stdout) the range of allowed values for each field
+   in the snapshot file.  The array below should be kept in sync
+   with any changes made to the read_num() calls in the parsing
+   loop inside read_incr_db_2().
+
+   (This function is invoked via the --show-snapshot-field-ranges
+   command line option.) */
+
+struct field_range
+{
+  char const *fieldname;
+  intmax_t min_val;
+  uintmax_t max_val;
+};
+
+static struct field_range const field_ranges[] = {
+  { "nfs", 0, 1 },
+  { "timestamp_sec", TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t) },
+  { "timestamp_nsec", 0, BILLION - 1 },
+  { "dev", TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t) },
+  { "ino", TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t) },
+  { NULL, 0, 0 }
+};
+
+void
+show_snapshot_field_ranges (void)
+{
+  struct field_range const *p;
+  char minbuf[SYSINT_BUFSIZE];
+  char maxbuf[SYSINT_BUFSIZE];
+
+  printf("This tar's snapshot file field ranges are\n");
+  printf ("   (%-15s => [ %s, %s ]):\n\n", "field name", "min", "max");
+
+  for (p=field_ranges; p->fieldname != NULL; p++)
+    {
+      printf ("    %-15s => [ %s, %s ],\n", p->fieldname,
+             sysinttostr (p->min_val, p->min_val, p->max_val, minbuf),
+             sysinttostr (p->max_val, p->min_val, p->max_val, maxbuf));
+
+    }
+
+  printf("\n");
+}
+
 /* Read incremental snapshot file (directory file).
    If the file has older incremental version, make sure that it is processed
    correctly and that tar will use the most conservative backup method among
@@ -1080,13 +1323,16 @@ void
 read_directory_file (void)
 {
   int fd;
-  char *buf = 0;
-  size_t bufsize;
+  char *buf = NULL;
+  size_t bufsize = 0;
+  int flags = O_RDWR | O_CREAT;
 
+  if (incremental_level == 0)
+    flags |= O_TRUNC;
   /* Open the file for both read and write.  That way, we can write
      it later without having to reopen it, and don't have to worry if
      we chdir in the meantime.  */
-  fd = open (listed_incremental_option, O_RDWR | O_CREAT, MODE_RW);
+  fd = open (listed_incremental_option, flags, MODE_RW);
   if (fd < 0)
     {
       open_error (listed_incremental_option);
@@ -1101,6 +1347,13 @@ read_directory_file (void)
       return;
     }
 
+  /* Consume the first name from the name list and reset the
+     list afterwards.  This is done to change to the new
+     directory, if the first name is a chdir request (-C dir),
+     which is necessary to recreate absolute file names. */
+  name_from_list ();
+  blank_name_list ();
+
   if (0 < getline (&buf, &bufsize, listed_incremental_stream))
     {
       char *ebuf;
@@ -1140,8 +1393,7 @@ read_directory_file (void)
 
   if (ferror (listed_incremental_stream))
     read_error (listed_incremental_option);
-  if (buf)
-    free (buf);
+  free (buf);
 }
 
 /* Output incremental data for the directory ENTRY to the file DATA.
@@ -1154,31 +1406,34 @@ write_directory_file_entry (void *entry, void *data)
 
   if (DIR_IS_FOUND (directory))
     {
-      char buf[UINTMAX_STRSIZE_BOUND];
-      char *s;
+      char buf[SYSINT_BUFSIZE];
+      char const *s;
 
       s = DIR_IS_NFS (directory) ? "1" : "0";
       fwrite (s, 2, 1, fp);
-      s = (TYPE_SIGNED (time_t)
-          ? imaxtostr (directory->mtime.tv_sec, buf)
-          : umaxtostr (directory->mtime.tv_sec, buf));
+      s = sysinttostr (directory->mtime.tv_sec, TYPE_MINIMUM (time_t),
+                      TYPE_MAXIMUM (time_t), buf);
       fwrite (s, strlen (s) + 1, 1, fp);
-      s = umaxtostr (directory->mtime.tv_nsec, buf);
+      s = imaxtostr (directory->mtime.tv_nsec, buf);
       fwrite (s, strlen (s) + 1, 1, fp);
-      s = umaxtostr (directory->device_number, buf);
+      s = sysinttostr (directory->device_number,
+                      TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t), buf);
       fwrite (s, strlen (s) + 1, 1, fp);
-      s = umaxtostr (directory->inode_number, buf);
+      s = sysinttostr (directory->inode_number,
+                      TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t), buf);
       fwrite (s, strlen (s) + 1, 1, fp);
 
       fwrite (directory->name, strlen (directory->name) + 1, 1, fp);
-      if (directory->contents)
+      if (directory->dump)
        {
-         char *p;
-         for (p = directory->contents; *p; p += strlen (p) + 1)
-           {
-             if (strchr ("YND", *p))
-               fwrite (p, strlen (p) + 1, 1, fp);
-           }
+         const char *p;
+         struct dumpdir_iter *itr;
+
+         for (p = dumpdir_first (directory->dump, 0, &itr);
+              p;
+              p = dumpdir_next (itr))
+           fwrite (p, strlen (p) + 1, 1, fp);
+         free (itr);
        }
       fwrite ("\0\0", 2, 1, fp);
     }
@@ -1196,7 +1451,7 @@ write_directory_file (void)
   if (! fp)
     return;
 
-  if (fseek (fp, 0L, SEEK_SET) != 0)
+  if (fseeko (fp, 0L, SEEK_SET) != 0)
     seek_error (listed_incremental_option);
   if (sys_truncate (fileno (fp)) != 0)
     truncate_error (listed_incremental_option);
@@ -1238,7 +1493,7 @@ get_gnu_dumpdir (struct tar_stat_info *stat_info)
   to = archive_dir;
 
   set_next_block_after (current_header);
-  mv_begin (stat_info);
+  mv_begin_read (stat_info);
 
   for (; size > 0; size -= copied)
     {
@@ -1353,7 +1608,8 @@ dumpdir_ok (char *dumpdir)
     }
 
   if (has_tempdir)
-    WARN ((0, 0, _("Malformed dumpdir: 'X' never used")));
+    WARNOPT (WARN_BAD_DUMPDIR,
+            (0, 0, _("Malformed dumpdir: 'X' never used")));
 
   return true;
 }
@@ -1366,11 +1622,12 @@ try_purge_directory (char const *directory_name)
   char *current_dir;
   char *cur, *arc, *p;
   char *temp_stub = NULL;
+  struct dumpdir *dump;
 
   if (!is_dumpdir (&current_stat_info))
     return false;
 
-  current_dir = savedir (directory_name);
+  current_dir = tar_savedir (directory_name, 0);
 
   if (!current_dir)
     /* The directory doesn't exist now.  It'll be created.  In any
@@ -1442,16 +1699,16 @@ try_purge_directory (char const *directory_name)
   free (temp_stub);
 
   /* Process deletes */
+  dump = dumpdir_create (current_stat_info.dumpdir);
   p = NULL;
   for (cur = current_dir; *cur; cur += strlen (cur) + 1)
     {
       const char *entry;
       struct stat st;
-      if (p)
-       free (p);
+      free (p);
       p = new_name (directory_name, cur);
 
-      if (deref_stat (false, p, &st))
+      if (deref_stat (p, &st) != 0)
        {
          if (errno != ENOENT) /* FIXME: Maybe keep a list of renamed
                                  dirs and check it here? */
@@ -1463,7 +1720,7 @@ try_purge_directory (char const *directory_name)
          continue;
        }
 
-      if (!(entry = dumpdir_locate (current_stat_info.dumpdir, cur))
+      if (!(entry = dumpdir_locate (dump, cur))
          || (*entry == 'D' && !S_ISDIR (st.st_mode))
          || (*entry == 'Y' && S_ISDIR (st.st_mode)))
        {
@@ -1489,6 +1746,7 @@ try_purge_directory (char const *directory_name)
        }
     }
   free (p);
+  dumpdir_free (dump);
 
   free (current_dir);
   return true;
This page took 0.060719 seconds and 4 git commands to generate.