]> Dogcows Code - chaz/tar/blobdiff - src/incremen.c
Bugfixes.
[chaz/tar] / src / incremen.c
index d354efa2bde79a725a4ee4292228847664116c42..7e1e36641e74b71b74d3d2f879adda761b0c24a9 100644 (file)
@@ -1,11 +1,11 @@
 /* GNU dump extensions to tar.
 
    Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
-   2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+   2003, 2004, 2005, 2006, 2007, 2008 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 2, or (at your option) any later
+   Free Software Foundation; either version 3, or (at your option) any later
    version.
 
    This program is distributed in the hope that it will be useful, but
@@ -18,7 +18,6 @@
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
 
 #include <system.h>
-#include <getline.h>
 #include <hash.h>
 #include <quotearg.h>
 #include "common.h"
@@ -46,26 +45,160 @@ enum children
 #define DIR_IS_FOUND(d) ((d)->flags & DIRF_FOUND)
 #define DIR_IS_NEW(d) ((d)->flags & DIRF_NEW)
 #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 */
     struct directory *orig;     /* If the directory was renamed, points to
                                   the original directory structure */
-    char name[1];              /* file name of directory */
+    const char *tagfile;        /* Tag file, if the directory falls under
+                                  exclusion_tag_under */
+    char *name;                        /* file name of directory */
   };
 
+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;
+}
+
+struct dumpdir *
+dumpdir_create (const char *contents)
+{
+  return dumpdir_create0 (contents, "YND");
+}
+
+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 */
+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 */
+};
+
+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;
+}
+
+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;
 
@@ -117,24 +250,78 @@ static struct directory *
 make_directory (const char *name)
 {
   size_t namelen = strlen (name);
-  size_t size = offsetof (struct directory, name) + namelen + 1;
-  struct directory *directory = xmalloc (size);
-  strcpy (directory->name, name);
-  if (ISSLASH (directory->name[namelen-1]))
-    directory->name[namelen-1] = 0;
+  struct directory *directory = xmalloc (sizeof (*directory));
+  directory->next = NULL;
+  directory->dump = directory->idump = NULL;
+  directory->orig = NULL;
   directory->flags = false;
+  if (namelen && ISSLASH (name[namelen - 1]))
+    namelen--;
+  directory->name = xmalloc (namelen + 1);
+  memcpy (directory->name, name, namelen);
+  directory->name[namelen] = 0;
+  directory->tagfile = NULL;
   return directory;
 }
-  
+
+static void
+free_directory (struct directory *dir)
+{
+  free (dir->name);
+  free (dir);
+}
+
+static struct directory *
+attach_directory (const char *name)
+{
+  struct directory *dir = make_directory (name);
+  if (dirtail)
+    dirtail->next = dir;
+  else
+    dirhead = dir;
+  dirtail = dir;
+  return dir;
+}
+                
+\f
+static void
+replace_prefix (char **pname, const char *samp, size_t slen,
+               const char *repl, size_t rlen)
+{
+  char *name = *pname;
+  size_t nlen = strlen (name);
+  if (nlen > slen && memcmp (name, samp, slen) == 0 && ISSLASH (name[slen]))
+    {
+      if (rlen > slen)
+       {
+         name = xrealloc (name, nlen - slen + rlen + 1);
+         *pname = name;
+       }
+      memmove (name + rlen, name + slen, nlen - slen + 1);
+      memcpy (name, repl, rlen);
+    }
+}
+
+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);
+}
+
 /* 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;
@@ -145,14 +332,10 @@ 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,
@@ -181,7 +364,7 @@ find_directory (const char *name)
     {
       struct directory *dir = make_directory (name);
       struct directory *ret = hash_lookup (directory_table, dir);
-      free (dir);
+      free_directory (dir);
       return ret;
     }
 }
@@ -196,10 +379,11 @@ find_directory_meta (dev_t dev, ino_t ino)
   else
     {
       struct directory *dir = make_directory ("");
+      struct directory *ret;
       dir->device_number = dev;
       dir->inode_number = ino;
-      struct directory *ret = hash_lookup (directory_meta_table, dir);
-      free (dir);
+      ret = hash_lookup (directory_meta_table, dir);
+      free_directory (dir);
       return ret;
     }
 }
@@ -208,7 +392,7 @@ void
 update_parent_directory (const char *name)
 {
   struct directory *directory;
-  char *p, *name_buffer;
+  char *p;
 
   p = dir_name (name);
   directory = find_directory (p);
@@ -227,17 +411,17 @@ static struct directory *
 procdir (char *name_buffer, struct stat *stat_data,
         dev_t device,
         enum children children,
-        bool verbose)
+        bool verbose,
+        char *entry)
 {
   struct directory *directory;
   bool nfs = NFS_FILE_STAT (*stat_data);
-  struct name *np;
 
   if ((directory = find_directory (name_buffer)) != NULL)
     {
       if (DIR_IS_INITED (directory))
        return directory;
-      
+
       /* With NFS, the same file can have two different devices
         if an NFS directory is mounted in multiple locations,
         which is relatively common when automounting.
@@ -245,7 +429,8 @@ procdir (char *name_buffer, struct stat *stat_data,
         directories, consider all NFS devices as equal,
         relying on the i-node to establish differences.  */
 
-      if (! (((DIR_IS_NFS (directory) & nfs)
+      if (! ((!check_device_option
+             || (DIR_IS_NFS (directory) && nfs)
              || directory->device_number == stat_data->st_dev)
             && directory->inode_number == stat_data->st_ino))
        {
@@ -254,12 +439,16 @@ 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))
+               {
+                 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);
+                 dirlist_replace_prefix (d->name, name_buffer);
+               }
              directory->children = CHANGED_CHILDREN;
            }
          else
@@ -276,7 +465,7 @@ procdir (char *name_buffer, struct stat *stat_data,
        }
       else
        directory->children = CHANGED_CHILDREN;
-      
+
       DIR_SET_FLAG (directory, DIRF_FOUND);
     }
   else
@@ -294,12 +483,16 @@ 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))
+           {
+             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);
+             dirlist_replace_prefix (d->name, name_buffer);
+           }
          directory->children = CHANGED_CHILDREN;
        }
       else
@@ -308,7 +501,7 @@ procdir (char *name_buffer, struct stat *stat_data,
          if (verbose)
            WARN ((0, 0, _("%s: Directory is new"),
                   quotearg_colon (name_buffer)));
-         directory->children = 
+         directory->children =
            (listed_incremental_option
             || (OLDER_STAT_TIME (*stat_data, m)
                 || (after_date_option
@@ -322,60 +515,47 @@ procdir (char *name_buffer, struct stat *stat_data,
      omit it... */
   if (one_file_system_option && device != stat_data->st_dev
       /* ... except if it was explicitely given in the command line */
-      && !((np = name_scan (name_buffer, true)) && np->explicit))
+      && !is_individual_file (name_buffer))
     directory->children = NO_CHILDREN;
-  else if (children == ALL_CHILDREN) 
+  else if (children == ALL_CHILDREN)
     directory->children = ALL_CHILDREN;
 
   DIR_SET_FLAG (directory, DIRF_INIT);
-  
-  return directory;
-}
 
-/* 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)
+  {
+    const char *tag_file_name;
+
+    switch (check_exclusion_tags (name_buffer, &tag_file_name))
       {
-       /* Ignore 'R' (rename) 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 (*dump != 'R')
-         {
-           int rc = strcmp (dump + 1, name);
-           if (rc == 0)
-             return dump;
-           if (rc > 1)
-             break;
-         }
-       dump += strlen (dump) + 1;
-      }
-  return NULL;
-}
+      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;
 
-/* Return size in bytes of the dumpdir array P */
-size_t
-dumpdir_size (const char *p)
-{
-  size_t totsize = 0;
+      case exclusion_tag_contents:
+       exclusion_tag_warning (name_buffer, tag_file_name,
+                              _("contents not dumped"));
+       directory->children = NO_CHILDREN;
+       break;
 
-  while (*p)
-    {
-      size_t size = strlen (p) + 1;
-      totsize += size;
-      p += size;
-    }
-  return totsize + 1;
-}
+      case exclusion_tag_under:
+       exclusion_tag_warning (name_buffer, tag_file_name,
+                              _("contents not dumped"));
+       directory->tagfile = tag_file_name;
+       break;
 
-static int
-compare_dirnames (const void *first, const void *second)
-{
-  return strcmp (*(const char**)first, *(const char**)second);
+      case exclusion_tag_none:
+       break;
+      }
+  }
+
+  return directory;
 }
 
 /* Compare dumpdir array from DIRECTORY with directory listing DIR and
@@ -383,10 +563,10 @@ 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
-   alphabetically. 
+   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
@@ -398,15 +578,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;
@@ -432,9 +612,16 @@ makedumpdir (struct directory *directory, const char *dir)
       const char *loc = dumpdir_locate (dump, array[i]);
       if (loc)
        {
-         *new_dump_ptr++ = ' ';
-         dump = loc + strlen (loc) + 1;
+         if (directory->tagfile)
+           *new_dump_ptr = strcmp (directory->tagfile, array[i]) == 0 ?
+                               ' ' : 'I';
+         else
+           *new_dump_ptr = ' ';
+         new_dump_ptr++;
        }
+      else if (directory->tagfile)
+       *new_dump_ptr++ = strcmp (directory->tagfile, array[i]) == 0 ?
+                              ' ' : 'I';
       else
        *new_dump_ptr++ = 'Y'; /* New entry */
 
@@ -443,36 +630,36 @@ 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 (array);
 }
 
 /* Recursively scan the given directory. */
-static char *
-scan_directory (char *dir_name, dev_t device)
+static const char *
+scan_directory (char *dir, dev_t device)
 {
-  char *dirp = savedir (dir_name);     /* for scanning 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;
   struct directory *directory;
-  
+
   if (! dirp)
-    savedir_error (dir_name);
+    savedir_error (dir);
 
-  name_buffer_size = strlen (dir_name) + NAME_FIELD_SIZE;
+  name_buffer_size = strlen (dir) + NAME_FIELD_SIZE;
   name_buffer = xmalloc (name_buffer_size + 2);
-  strcpy (name_buffer, dir_name);
-  if (! ISSLASH (dir_name[strlen (dir_name) - 1]))
+  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 
+      /* FIXME: used to be
            children = CHANGED_CHILDREN;
         but changed to: */
       free (name_buffer);
@@ -480,19 +667,22 @@ scan_directory (char *dir_name, dev_t device)
       return NULL;
     }
 
-  directory = procdir (name_buffer, &stat_data, device, NO_CHILDREN, false);
-    
+  directory = procdir (name_buffer, &stat_data, device, NO_CHILDREN, false,
+                      NULL);
+
   if (dirp && directory->children != NO_CHILDREN)
     {
-      char  *entry;    /* directory entry being scanned */
+      char *entry;     /* directory entry being scanned */
       size_t entrylen; /* length of directory entry */
+      dumpdir_iter_t itr;
 
       makedumpdir (directory, dirp);
 
-      for (entry = directory->contents;
-          (entrylen = strlen (entry)) != 0;
-          entry += entrylen + 1)
+      for (entry = dumpdir_first (directory->dump, 1, &itr);
+          entry;
+          entry = dumpdir_next (itr))
        {
+         entrylen = strlen (entry);
          if (name_buffer_size <= entrylen - 1 + name_length)
            {
              do
@@ -501,8 +691,10 @@ scan_directory (char *dir_name, dev_t device)
              name_buffer = xrealloc (name_buffer, name_buffer_size + 2);
            }
          strcpy (name_buffer + name_length, entry + 1);
-         
-         if (excluded_name (name_buffer))
+
+         if (*entry == 'I') /* Ignored entry */
+           *entry = 'N';
+         else if (excluded_name (name_buffer))
            *entry = 'N';
          else
            {
@@ -512,23 +704,23 @@ scan_directory (char *dir_name, dev_t device)
                  *entry = 'N';
                  continue;
                }
-             
+
              if (S_ISDIR (stat_data.st_mode))
                {
+                 *entry = 'D';
                  procdir (name_buffer, &stat_data, device,
                           directory->children,
-                          verbose_option);
-                 *entry = 'D';
+                          verbose_option, entry);
                }
-             
+
              else if (one_file_system_option && device != stat_data.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 (stat_data, m)
                       && (!after_date_option
                           || OLDER_STAT_TIME (stat_data, c)))
@@ -537,85 +729,47 @@ scan_directory (char *dir_name, dev_t device)
                *entry = 'Y';
            }
        }
+      free (itr);
     }
-  
+
   free (name_buffer);
   if (dirp)
     free (dirp);
 
-  return directory->contents;
+  return directory->dump ? directory->dump->contents : NULL;
 }
 
-char *
-get_directory_contents (char *dir_name, dev_t device)
+const char *
+get_directory_contents (char *dir, dev_t device)
 {
-  return scan_directory (dir_name, device);
+  return scan_directory (dir, device);
 }
 
 \f
-static bool
-try_pos (char *name, int pos, const char *dumpdir)
-{
-  int i;
-  static char namechars[] =
-    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
-  if (pos > 0)
-    for (i = 0; i < sizeof namechars; i++)
-      {
-       name[pos] = namechars[i];
-       if (!dumpdir_locate (dumpdir, name)
-           || try_pos (name, pos-1, dumpdir))
-         return true;
-      }
-  
-  return false;
-}
-  
-static bool
-create_temp_name (char *name, const char *dumpdir)
-{
-  size_t pos = strlen (name) - 6;
-  return try_pos (name + pos, 5, dumpdir);
-}
-
-char *
-make_tmp_dir_name (const char *name)
-{
-  char *dirname = dir_name (name);
-  char *tmp_name = NULL;
-  struct directory *dir = find_directory (dirname);
-      
-  tmp_name = new_name (dirname, "000000");
-  if (!create_temp_name (tmp_name, dir ? dir->contents : NULL))
-    {
-      free (tmp_name);
-      tmp_name = NULL;
-    }
-  free (dirname);
-  return tmp_name;
-}
-
 static void
 obstack_code_rename (struct obstack *stk, char *from, char *to)
 {
+  char *s;
+
+  s = from[0] == 0 ? from :
+                     safer_name_suffix (from, false, absolute_names_option);
   obstack_1grow (stk, 'R');
-  obstack_grow (stk, from, strlen (from) + 1);
+  obstack_grow (stk, s, strlen (s) + 1);
+
+  s = to[0] == 0 ? to:
+                   safer_name_suffix (to, false, absolute_names_option);
   obstack_1grow (stk, 'T');
-  obstack_grow (stk, to, strlen (to) + 1);
+  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;
 
-      /* Detect eventual cycles and clear DIRF_RENAMED flag, so this entries
+      /* Detect eventual cycles and clear DIRF_RENAMED flag, so these entries
         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
@@ -636,17 +790,19 @@ rename_handler (void *data, void *proc_data)
 
          /* Break the cycle by using a temporary name for one of its
             elements.
-            FIXME: Leave the choice of the name to the extractor. */
-         temp_name = make_tmp_dir_name (dir->name);
-         obstack_code_rename (stk, dir->name, temp_name);
+            First, create a temp name stub entry. */
+         temp_name = dir_name (dir->name);
+         obstack_1grow (stk, 'X');
+         obstack_grow (stk, temp_name, strlen (temp_name) + 1);
+
+         obstack_code_rename (stk, dir->name, "");
 
          for (p = dir; p != prev; p = p->orig)
            obstack_code_rename (stk, p->orig->name, p->name);
 
-         obstack_code_rename (stk, temp_name, prev->name);
+         obstack_code_rename (stk, "", prev->name);
        }
     }
-  return true;
 }
 
 const char *
@@ -654,10 +810,11 @@ append_incremental_renames (const char *dump)
 {
   struct obstack stk;
   size_t size;
-
-  if (directory_table == NULL)
-    return dump;
+  struct directory *dp;
   
+  if (dirhead == NULL)
+    return dump;
+
   obstack_init (&stk);
   if (dump)
     {
@@ -666,8 +823,10 @@ append_incremental_renames (const char *dump)
     }
   else
     size = 0;
-  
-  hash_do_for_each (directory_table, rename_handler, &stk);
+
+  for (dp = dirhead; dp; dp = dp->next)
+    store_rename (dp, &stk);
+
   if (obstack_object_size (&stk) != size)
     {
       obstack_1grow (&stk, 0);
@@ -698,7 +857,8 @@ read_incr_db_01 (int version, const char *initbuf)
 {
   int n;
   uintmax_t u;
-  time_t t = u;
+  time_t sec;
+  long int nsec;
   char *buf = 0;
   size_t bufsize;
   char *ebuf;
@@ -719,41 +879,49 @@ read_incr_db_01 (int version, const char *initbuf)
       buf = strdup (initbuf);
       bufsize = strlen (buf) + 1;
     }
-  
-  t = u = (errno = 0, strtoumax (buf, &ebuf, 10));
-  if (buf == ebuf || (u == 0 && errno == EINVAL))
-    ERROR ((0, 0, "%s:%ld: %s",
+
+  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)
+    ERROR ((0, errno, "%s:%ld: %s",
            quotearg_colon (listed_incremental_option),
            lineno,
            _("Invalid time stamp")));
-  else if (t != u)
-    ERROR ((0, 0, "%s:%ld: %s",
-           quotearg_colon (listed_incremental_option),
-           lineno,
-           _("Time stamp out of range")));
-  else if (version == 1)
-    {
-      newer_mtime_option.tv_sec = t;
-
-      t = u = (errno = 0, strtoumax (buf, &ebuf, 10));
-      if (buf == ebuf || (u == 0 && errno == EINVAL))
-       ERROR ((0, 0, "%s:%ld: %s",
-               quotearg_colon (listed_incremental_option),
-               lineno,
-               _("Invalid time stamp")));
-      else if (t != u)
-       ERROR ((0, 0, "%s:%ld: %s",
-               quotearg_colon (listed_incremental_option),
-               lineno,
-               _("Time stamp out of range")));
-      newer_mtime_option.tv_nsec = t;
-    }
   else
     {
-      /* pre-1 incremental format does not contain nanoseconds */
-      newer_mtime_option.tv_sec = t;
-      newer_mtime_option.tv_nsec = 0;
+      sec = u;
+
+      if (version == 1 && *ebuf)
+       {
+         char const *buf_ns = ebuf + 1;
+         errno = 0;
+         u = strtoumax (buf_ns, &ebuf, 10);
+         if (!errno && BILLION <= u)
+           errno = ERANGE;
+         if (errno || buf_ns == ebuf)
+           {
+             ERROR ((0, errno, "%s:%ld: %s",
+                     quotearg_colon (listed_incremental_option),
+                     lineno,
+                     _("Invalid time stamp")));
+             sec = TYPE_MINIMUM (time_t);
+           }
+         else
+           nsec = u;
+       }
+      else
+       {
+         /* pre-1 incremental format does not contain nanoseconds */
+         nsec = 0;
+       }
     }
+  newer_mtime_option.tv_sec = sec;
+  newer_mtime_option.tv_nsec = nsec;
+
 
   while (0 < (n = getline (&buf, &bufsize, listed_incremental_stream)))
     {
@@ -762,65 +930,79 @@ read_incr_db_01 (int version, const char *initbuf)
       bool nfs = buf[0] == '+';
       char *strp = buf + nfs;
       struct timespec mtime;
-      
+
       lineno++;
-      
+
       if (buf[n - 1] == '\n')
        buf[n - 1] = '\0';
-      
+
       if (version == 1)
        {
          errno = 0;
-         mtime.tv_sec = u = strtoumax (strp, &ebuf, 10);
-         if (!isspace (*ebuf))
-           ERROR ((0, 0, "%s:%ld: %s",
-                   quotearg_colon (listed_incremental_option), lineno,
-                   _("Invalid modification time (seconds)")));
-         else if (mtime.tv_sec != u)
-           ERROR ((0, 0, "%s:%ld: %s",
-                   quotearg_colon (listed_incremental_option), lineno,
-                   _("Modification time (seconds) out of range")));
+         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;
          strp = ebuf;
-         
+
          errno = 0;
-         mtime.tv_nsec = u = strtoumax (strp, &ebuf, 10);
-         if (!isspace (*ebuf))
-           ERROR ((0, 0, "%s:%ld: %s",
-                   quotearg_colon (listed_incremental_option), lineno,
-                   _("Invalid modification time (nanoseconds)")));
-         else if (mtime.tv_nsec != u)
-           ERROR ((0, 0, "%s:%ld: %s",
-                   quotearg_colon (listed_incremental_option), lineno,
-                   _("Modification time (nanoseconds) out of range")));
+         u = strtoumax (strp, &ebuf, 10);
+         if (!errno && BILLION <= u)
+           errno = ERANGE;
+         if (errno || strp == ebuf || *ebuf != ' ')
+           {
+             ERROR ((0, errno, "%s:%ld: %s",
+                     quotearg_colon (listed_incremental_option), lineno,
+                     _("Invalid modification time (nanoseconds)")));
+             nsec = -1;
+           }
+         else
+           nsec = u;
+         mtime.tv_sec = sec;
+         mtime.tv_nsec = nsec;
          strp = ebuf;
        }
       else
        memset (&mtime, 0, sizeof mtime);
-      
+
       errno = 0;
-      dev = u = strtoumax (strp, &ebuf, 10);
-      if (!isspace (*ebuf))
-       ERROR ((0, 0, "%s:%ld: %s",
-               quotearg_colon (listed_incremental_option), lineno,
-               _("Invalid device number")));
-      else if (dev != u)
-       ERROR ((0, 0, "%s:%ld: %s",
-               quotearg_colon (listed_incremental_option), lineno,
-               _("Device number out of range")));
+      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;
       strp = ebuf;
 
       errno = 0;
-      ino = u = strtoumax (strp, &ebuf, 10);
-      if (!isspace (*ebuf))
-       ERROR ((0, 0, "%s:%ld: %s",
-               quotearg_colon (listed_incremental_option), lineno,
-               _("Invalid inode number")));
-      else if (ino != u)
-       ERROR ((0, 0, "%s:%ld: %s",
-               quotearg_colon (listed_incremental_option), lineno,
-               _("Inode number out of range")));
+      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;
       strp = ebuf;
-      
+
       strp++;
       unquote_string (strp);
       note_directory (strp, mtime, dev, ino, nfs, false, NULL);
@@ -847,31 +1029,128 @@ read_obstack (FILE *fp, struct obstack *stk, size_t *pcount)
 }
 
 /* Read from file FP a nul-terminated string and convert it to
-   uintmax_t. Return the resulting value in PVAL.
+   intmax_t.  Return the resulting value in PVAL.  Assume '-' has
+   already been read.
+
+   Throw a fatal error if the string cannot be converted or if the
+   converted value is less than MIN_VAL.  */
+
+static void
+read_negative_num (FILE *fp, intmax_t min_val, intmax_t *pval)
+{
+  int c;
+  size_t i;
+  char buf[INT_BUFSIZE_BOUND (intmax_t)];
+  char *ep;
+  buf[0] = '-';
+
+  for (i = 1; ISDIGIT (c = getc (fp)); 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.
 
-   Throw fatal error if the string cannot be converted.
-   
    Return the last character read or EOF on end of file. */
 
 static int
-read_num (FILE *fp, uintmax_t *pval)
+read_unsigned_num (int c, FILE *fp, uintmax_t max_val, uintmax_t *pval)
 {
-  int c;
   size_t i;
   char buf[UINTMAX_STRSIZE_BOUND], *ep;
 
-  for (i = 0, c = getc (fp); c != EOF && c != 0; c = getc (fp), i++)
+  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;
+      c = getc (fp);
+    }
+
+  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")));
     }
+
   buf[i] = 0;
+  errno = 0;
   *pval = strtoumax (buf, &ep, 10);
-  if (*ep)
-    FATAL_ERROR ((0, 0, _("Unexpected field value in snapshot file")));
+  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.
+
+   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_num (FILE *fp, uintmax_t max_val, uintmax_t *pval)
+{
+  return read_unsigned_num (getc (fp), fp, max_val, pval);
+}
+
+/* Read from FP two NUL-terminated strings representing a struct
+   timespec.  Return the resulting value in PVAL.
+
+   Throw a fatal error if the string cannot be converted.  */
+
+static void
+read_timespec (FILE *fp, struct timespec *pval)
+{
+  int c = getc (fp);
+  intmax_t i;
+  uintmax_t u;
+
+  if (c == '-')
+    {
+      read_negative_num (fp, TYPE_MINIMUM (time_t), &i);
+      c = 0;
+      pval->tv_sec = i;
+    }
+  else
+    {
+      c = read_unsigned_num (c, fp, TYPE_MAXIMUM (time_t), &u);
+      pval->tv_sec = u;
+    }
+
+  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
@@ -879,28 +1158,10 @@ read_incr_db_2 ()
 {
   uintmax_t u;
   struct obstack stk;
-  
-  obstack_init (&stk);
 
-  if (read_num (listed_incremental_stream, &u))
-    FATAL_ERROR ((0, 0, "%s: %s",
-                 quotearg_colon (listed_incremental_option), 
-                 _("Error reading time stamp")));
-  newer_mtime_option.tv_sec = u;
-  if (newer_mtime_option.tv_sec != u)
-    FATAL_ERROR ((0, 0, "%s: %s",
-                 quotearg_colon (listed_incremental_option),
-                 _("Time stamp out of range")));
+  obstack_init (&stk);
 
-  if (read_num (listed_incremental_stream, &u))
-    FATAL_ERROR ((0, 0, "%s: %s",
-                 quotearg_colon (listed_incremental_option), 
-                 _("Error reading time stamp")));
-  newer_mtime_option.tv_nsec = u;
-  if (newer_mtime_option.tv_nsec != u)
-    FATAL_ERROR ((0, 0, "%s: %s",
-                 quotearg_colon (listed_incremental_option),
-                 _("Time stamp out of range")));
+  read_timespec (listed_incremental_stream, &newer_mtime_option);
 
   for (;;)
     {
@@ -911,43 +1172,21 @@ read_incr_db_2 ()
       char *name;
       char *content;
       size_t s;
-      
-      if (read_num (listed_incremental_stream, &u))
+
+      if (read_num (listed_incremental_stream, 1, &u))
        return; /* Normal return */
 
       nfs = u;
-      
-      if (read_num (listed_incremental_stream, &u))
-       break;
-      mtime.tv_sec = u;
-      if (mtime.tv_sec != u)
-       FATAL_ERROR ((0, 0, "%s: %s",
-                     quotearg_colon (listed_incremental_option), 
-                     _("Modification time (seconds) out of range")));
-      
-      if (read_num (listed_incremental_stream, &u))
-       break;
-      mtime.tv_nsec = u;
-      if (mtime.tv_nsec != u)
-       FATAL_ERROR ((0, 0, "%s: %s",
-                     quotearg_colon (listed_incremental_option), 
-                     _("Modification time (nanoseconds) out of range")));
 
-      if (read_num (listed_incremental_stream, &u))
+      read_timespec (listed_incremental_stream, &mtime);
+
+      if (read_num (listed_incremental_stream, TYPE_MAXIMUM (dev_t), &u))
        break;
       dev = u;
-      if (dev != u)
-       FATAL_ERROR ((0, 0, "%s: %s",
-                     quotearg_colon (listed_incremental_option), 
-                     _("Device number out of range")));
 
-      if (read_num (listed_incremental_stream, &u))
+      if (read_num (listed_incremental_stream, TYPE_MAXIMUM (ino_t), &u))
        break;
       ino = u;
-      if (ino != u)
-       FATAL_ERROR ((0, 0, "%s: %s",
-                     quotearg_colon (listed_incremental_option), 
-                     _("Inode number out of range")));
 
       if (read_obstack (listed_incremental_stream, &stk, &s))
        break;
@@ -958,16 +1197,16 @@ read_incr_db_2 ()
        ;
       if (getc (listed_incremental_stream) != 0)
        FATAL_ERROR ((0, 0, "%s: %s",
-                     quotearg_colon (listed_incremental_option), 
+                     quotearg_colon (listed_incremental_option),
                      _("Missing record terminator")));
-      
+
       content = obstack_finish (&stk);
       note_directory (name, mtime, dev, ino, nfs, false, content);
       obstack_free (&stk, content);
     }
   FATAL_ERROR ((0, 0, "%s: %s",
-               quotearg_colon (listed_incremental_option), 
-               _("Unexpected EOF")));
+               quotearg_colon (listed_incremental_option),
+               _("Unexpected EOF in snapshot file")));
 }
 
 /* Read incremental snapshot file (directory file).
@@ -982,7 +1221,6 @@ read_directory_file (void)
   int fd;
   char *buf = 0;
   size_t bufsize;
-  long lineno = 1;
 
   /* 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
@@ -1005,7 +1243,7 @@ read_directory_file (void)
   if (0 < getline (&buf, &bufsize, listed_incremental_stream))
     {
       char *ebuf;
-      int incremental_version;
+      uintmax_t incremental_version;
 
       if (strncmp (buf, PACKAGE_NAME, sizeof PACKAGE_NAME - 1) == 0)
        {
@@ -1016,7 +1254,7 @@ read_directory_file (void)
            if (!*ebuf)
              ERROR((1, 0, _("Bad incremental file format")));
 
-         incremental_version = (errno = 0, strtoumax (ebuf+1, &ebuf, 10));
+         incremental_version = strtoumax (ebuf + 1, NULL, 10);
        }
       else
        incremental_version = 0;
@@ -1031,12 +1269,12 @@ read_directory_file (void)
        case TAR_INCREMENTAL_VERSION:
          read_incr_db_2 ();
          break;
-         
+
        default:
-         ERROR ((1, 0, _("Unsupported incremental format version: %d"),
+         ERROR ((1, 0, _("Unsupported incremental format version: %"PRIuMAX),
                  incremental_version));
        }
-      
+
     }
 
   if (ferror (listed_incremental_stream))
@@ -1060,7 +1298,9 @@ write_directory_file_entry (void *entry, void *data)
 
       s = DIR_IS_NFS (directory) ? "1" : "0";
       fwrite (s, 2, 1, fp);
-      s = umaxtostr (directory->mtime.tv_sec, buf);
+      s = (TYPE_SIGNED (time_t)
+          ? imaxtostr (directory->mtime.tv_sec, buf)
+          : umaxtostr (directory->mtime.tv_sec, buf));
       fwrite (s, strlen (s) + 1, 1, fp);
       s = umaxtostr (directory->mtime.tv_nsec, buf);
       fwrite (s, strlen (s) + 1, 1, fp);
@@ -1070,14 +1310,16 @@ write_directory_file_entry (void *entry, void *data)
       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;
+         dumpdir_iter_t 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);
     }
@@ -1103,7 +1345,9 @@ write_directory_file (void)
   fprintf (fp, "%s-%s-%d\n", PACKAGE_NAME, PACKAGE_VERSION,
           TAR_INCREMENTAL_VERSION);
 
-  s = umaxtostr (start_time.tv_sec, buf);
+  s = (TYPE_SIGNED (time_t)
+       ? imaxtostr (start_time.tv_sec, buf)
+       : umaxtostr (start_time.tv_sec, buf));
   fwrite (s, strlen (s) + 1, 1, fp);
   s = umaxtostr (start_time.tv_nsec, buf);
   fwrite (s, strlen (s) + 1, 1, fp);
@@ -1170,74 +1414,203 @@ is_dumpdir (struct tar_stat_info *stat_info)
   return stat_info->is_dumpdir;
 }
 
+static bool
+dumpdir_ok (char *dumpdir)
+{
+  char *p;
+  int has_tempdir = 0;
+  int expect = 0;
+
+  for (p = dumpdir; *p; p += strlen (p) + 1)
+    {
+      if (expect && *p != expect)
+       {
+         ERROR ((0, 0,
+                 _("Malformed dumpdir: expected '%c' but found %#3o"),
+                 expect, *p));
+         return false;
+       }
+      switch (*p)
+       {
+       case 'X':
+         if (has_tempdir)
+           {
+             ERROR ((0, 0,
+                     _("Malformed dumpdir: 'X' duplicated")));
+             return false;
+           }
+         else
+           has_tempdir = 1;
+         break;
+
+       case 'R':
+         if (p[1] == 0)
+           {
+             if (!has_tempdir)
+               {
+                 ERROR ((0, 0,
+                         _("Malformed dumpdir: empty name in 'R'")));
+                 return false;
+               }
+             else
+               has_tempdir = 0;
+           }
+         expect = 'T';
+         break;
+
+       case 'T':
+         if (expect != 'T')
+           {
+             ERROR ((0, 0,
+                     _("Malformed dumpdir: 'T' not preceeded by 'R'")));
+             return false;
+           }
+         if (p[1] == 0 && !has_tempdir)
+           {
+             ERROR ((0, 0,
+                     _("Malformed dumpdir: empty name in 'T'")));
+             return false;
+           }
+         expect = 0;
+         break;
+
+       case 'N':
+       case 'Y':
+       case 'D':
+         break;
+
+       default:
+         /* FIXME: bail out? */
+         break;
+       }
+    }
+
+  if (expect)
+    {
+      ERROR ((0, 0,
+             _("Malformed dumpdir: expected '%c' but found end of data"),
+             expect));
+      return false;
+    }
+
+  if (has_tempdir)
+    WARN ((0, 0, _("Malformed dumpdir: 'X' never used")));
+
+  return true;
+}
+
 /* Examine the directories under directory_name and delete any
    files that were not there at the time of the back-up. */
-void
-purge_directory (char const *directory_name)
+static bool
+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))
-    {
-      skip_member ();
-      return;
-    }
+    return false;
 
   current_dir = savedir (directory_name);
 
   if (!current_dir)
-    {
-      /* The directory doesn't exist now.  It'll be created.  In any
-        case, we don't have to delete any files out of it.  */
+    /* The directory doesn't exist now.  It'll be created.  In any
+       case, we don't have to delete any files out of it.  */
+    return false;
 
-      skip_member ();
-      return;
-    }
+  /* Verify if dump directory is sane */
+  if (!dumpdir_ok (current_stat_info.dumpdir))
+    return false;
 
   /* Process renames */
   for (arc = current_stat_info.dumpdir; *arc; arc += strlen (arc) + 1)
     {
-      if (*arc == 'R')
+      if (*arc == 'X')
+       {
+#define TEMP_DIR_TEMPLATE "tar.XXXXXX"
+         size_t len = strlen (arc + 1);
+         temp_stub = xrealloc (temp_stub, len + 1 + sizeof TEMP_DIR_TEMPLATE);
+         memcpy (temp_stub, arc + 1, len);
+         temp_stub[len] = '/';
+         memcpy (temp_stub + len + 1, TEMP_DIR_TEMPLATE,
+                 sizeof TEMP_DIR_TEMPLATE);
+         if (!mkdtemp (temp_stub))
+           {
+             ERROR ((0, errno,
+                     _("Cannot create temporary directory using template %s"),
+                     quote (temp_stub)));
+             free (temp_stub);
+             free (current_dir);
+             return false;
+           }
+       }
+      else if (*arc == 'R')
        {
          char *src, *dst;
          src = arc + 1;
          arc += strlen (arc) + 1;
          dst = arc + 1;
 
+         /* Ensure that neither source nor destination are absolute file
+            names (unless permitted by -P option), and that they do not
+            contain dubious parts (e.g. ../).
+
+            This is an extra safety precaution. Besides, it might be
+            necessary to extract from archives created with tar versions
+            prior to 1.19. */
+
+         if (*src)
+           src = safer_name_suffix (src, false, absolute_names_option);
+         if (*dst)
+           dst = safer_name_suffix (dst, false, absolute_names_option);
+
+         if (*src == 0)
+           src = temp_stub;
+         else if (*dst == 0)
+           dst = temp_stub;
+
          if (!rename_directory (src, dst))
            {
+             free (temp_stub);
              free (current_dir);
              /* FIXME: Make sure purge_directory(dst) will return
                 immediately */
-             return;
+             return false;
            }
        }
     }
-  
+
+  free (temp_stub);
+
   /* Process deletes */
+  dump = dumpdir_create (current_stat_info.dumpdir);
   p = NULL;
   for (cur = current_dir; *cur; cur += strlen (cur) + 1)
     {
-      if (!dumpdir_locate (current_stat_info.dumpdir, cur))
-       {
-         struct stat st;
-         if (p)
-           free (p);
-         p = new_name (directory_name, cur);
+      const char *entry;
+      struct stat st;
+      if (p)
+       free (p);
+      p = new_name (directory_name, cur);
 
-         if (deref_stat (false, p, &st))
+      if (deref_stat (false, p, &st))
+       {
+         if (errno != ENOENT) /* FIXME: Maybe keep a list of renamed
+                                 dirs and check it here? */
            {
-             if (errno != ENOENT) /* FIXME: Maybe keep a list of renamed
-                                     dirs and check it here? */
-               {
-                 stat_diag (p);
-                 WARN ((0, 0, _("%s: Not purging directory: unable to stat"),
-                        quotearg_colon (p)));
-               }
-             continue;
+             stat_diag (p);
+             WARN ((0, 0, _("%s: Not purging directory: unable to stat"),
+                    quotearg_colon (p)));
            }
-         else if (one_file_system_option && st.st_dev != root_device)
+         continue;
+       }
+
+      if (!(entry = dumpdir_locate (dump, cur))
+         || (*entry == 'D' && !S_ISDIR (st.st_mode))
+         || (*entry == 'Y' && S_ISDIR (st.st_mode)))
+       {
+         if (one_file_system_option && st.st_dev != root_device)
            {
              WARN ((0, 0,
                     _("%s: directory is on a different device: not purging"),
@@ -1259,13 +1632,23 @@ purge_directory (char const *directory_name)
        }
     }
   free (p);
+  dumpdir_free (dump);
   
   free (current_dir);
+  return true;
+}
+
+void
+purge_directory (char const *directory_name)
+{
+  if (!try_purge_directory (directory_name))
+    skip_member ();
 }
 
 void
 list_dumpdir (char *buffer, size_t size)
 {
+  int state = 0;
   while (size)
     {
       switch (*buffer)
@@ -1275,7 +1658,13 @@ list_dumpdir (char *buffer, size_t size)
        case 'D':
        case 'R':
        case 'T':
-         fprintf (stdlis, "%c ", *buffer);
+       case 'X':
+         fprintf (stdlis, "%c", *buffer);
+         if (state == 0)
+           {
+             fprintf (stdlis, " ");
+             state = 1;
+           }
          buffer++;
          size--;
          break;
@@ -1284,6 +1673,7 @@ list_dumpdir (char *buffer, size_t size)
          fputc ('\n', stdlis);
          buffer++;
          size--;
+         state = 0;
          break;
 
        default:
This page took 0.068018 seconds and 4 git commands to generate.