]> Dogcows Code - chaz/tar/blobdiff - src/create.c
tar: don't worry about fdopendir closing its argument
[chaz/tar] / src / create.c
index 9e3922dd9d3c0d895a642236b800ae85686041a3..9dc928d347839ad4c8bc63f59cd790c5d0fef341 100644 (file)
@@ -1,13 +1,13 @@
 /* Create a tar archive.
 
    Copyright (C) 1985, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
 /* Create a tar archive.
 
    Copyright (C) 1985, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
-   2003, 2004, 2005 Free Software Foundation, Inc.
+   2003, 2004, 2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
 
    Written by John Gilmore, on 1985-08-25.
 
    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
 
    Written by John Gilmore, on 1985-08-25.
 
    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
    version.
 
    This program is distributed in the hope that it will be useful, but
 #include <system.h>
 
 #include <quotearg.h>
 #include <system.h>
 
 #include <quotearg.h>
-#include <utimens.h>
 
 #include "common.h"
 #include <hash.h>
 
 
 #include "common.h"
 #include <hash.h>
 
+/* Error number to use when an impostor is discovered.
+   Pretend the impostor isn't there.  */
+enum { IMPOSTOR_ERRNO = ENOENT };
+
 struct link
   {
     dev_t dev;
     ino_t ino;
 struct link
   {
     dev_t dev;
     ino_t ino;
-    size_t nlink;
+    nlink_t nlink;
     char name[1];
   };
     char name[1];
   };
+
+struct exclusion_tag
+{
+  const char *name;
+  size_t length;
+  enum exclusion_tag_type type;
+  bool (*predicate) (int fd);
+  struct exclusion_tag *next;
+};
+
+static struct exclusion_tag *exclusion_tags;
+
+void
+add_exclusion_tag (const char *name, enum exclusion_tag_type type,
+                  bool (*predicate) (int fd))
+{
+  struct exclusion_tag *tag = xmalloc (sizeof tag[0]);
+  tag->next = exclusion_tags;
+  tag->name = name;
+  tag->type = type;
+  tag->predicate = predicate;
+  tag->length = strlen (name);
+  exclusion_tags = tag;
+}
+
+void
+exclusion_tag_warning (const char *dirname, const char *tagname,
+                      const char *message)
+{
+  if (verbose_option)
+    WARNOPT (WARN_CACHEDIR,
+            (0, 0,
+             _("%s: contains a cache directory tag %s; %s"),
+             quotearg_colon (dirname),
+             quotearg_n (1, tagname),
+             message));
+}
+
+enum exclusion_tag_type
+check_exclusion_tags (struct tar_stat_info const *st, char const **tag_file_name)
+{
+  struct exclusion_tag *tag;
+
+  for (tag = exclusion_tags; tag; tag = tag->next)
+    {
+      int tagfd = subfile_open (st, tag->name, open_read_flags);
+      if (0 <= tagfd)
+       {
+         bool satisfied = !tag->predicate || tag->predicate (tagfd);
+         close (tagfd);
+         if (satisfied)
+           {
+             if (tag_file_name)
+               *tag_file_name = tag->name;
+             return tag->type;
+           }
+       }
+    }
+
+  return exclusion_tag_none;
+}
+
+/* Exclusion predicate to test if the named file (usually "CACHEDIR.TAG")
+   contains a valid header, as described at:
+       http://www.brynosaurus.com/cachedir
+   Applications can write this file into directories they create
+   for use as caches containing purely regenerable, non-precious data,
+   allowing us to avoid archiving them if --exclude-caches is specified. */
+
+#define CACHEDIR_SIGNATURE "Signature: 8a477f597d28d172789f06886806bc55"
+#define CACHEDIR_SIGNATURE_SIZE (sizeof CACHEDIR_SIGNATURE - 1)
+
+bool
+cachedir_file_p (int fd)
+{
+  char tagbuf[CACHEDIR_SIGNATURE_SIZE];
+
+  return
+    (read (fd, tagbuf, CACHEDIR_SIGNATURE_SIZE) == CACHEDIR_SIGNATURE_SIZE
+     && memcmp (tagbuf, CACHEDIR_SIGNATURE, CACHEDIR_SIGNATURE_SIZE) == 0);
+}
+
 \f
 /* The maximum uintmax_t value that can be represented with DIGITS digits,
    assuming that each digit is BITS_PER_DIGIT wide.  */
 \f
 /* The maximum uintmax_t value that can be represented with DIGITS digits,
    assuming that each digit is BITS_PER_DIGIT wide.  */
@@ -109,6 +194,81 @@ to_base256 (int negative, uintmax_t value, char *where, size_t size)
   while (i);
 }
 
   while (i);
 }
 
+#define GID_TO_CHARS(val, where) gid_to_chars (val, where, sizeof (where))
+#define MAJOR_TO_CHARS(val, where) major_to_chars (val, where, sizeof (where))
+#define MINOR_TO_CHARS(val, where) minor_to_chars (val, where, sizeof (where))
+#define MODE_TO_CHARS(val, where) mode_to_chars (val, where, sizeof (where))
+#define UID_TO_CHARS(val, where) uid_to_chars (val, where, sizeof (where))
+
+#define UNAME_TO_CHARS(name,buf) string_to_chars (name, buf, sizeof(buf))
+#define GNAME_TO_CHARS(name,buf) string_to_chars (name, buf, sizeof(buf))
+
+static bool
+to_chars (int negative, uintmax_t value, size_t valsize,
+         uintmax_t (*substitute) (int *),
+         char *where, size_t size, const char *type);
+
+static bool
+to_chars_subst (int negative, int gnu_format, uintmax_t value, size_t valsize,
+               uintmax_t (*substitute) (int *),
+               char *where, size_t size, const char *type)
+{
+  uintmax_t maxval = (gnu_format
+                     ? MAX_VAL_WITH_DIGITS (size - 1, LG_256)
+                     : MAX_VAL_WITH_DIGITS (size - 1, LG_8));
+  char valbuf[UINTMAX_STRSIZE_BOUND + 1];
+  char maxbuf[UINTMAX_STRSIZE_BOUND];
+  char minbuf[UINTMAX_STRSIZE_BOUND + 1];
+  char const *minval_string;
+  char const *maxval_string = STRINGIFY_BIGINT (maxval, maxbuf);
+  char const *value_string;
+
+  if (gnu_format)
+    {
+      uintmax_t m = maxval + 1 ? maxval + 1 : maxval / 2 + 1;
+      char *p = STRINGIFY_BIGINT (m, minbuf + 1);
+      *--p = '-';
+      minval_string = p;
+    }
+  else
+    minval_string = "0";
+
+  if (negative)
+    {
+      char *p = STRINGIFY_BIGINT (- value, valbuf + 1);
+      *--p = '-';
+      value_string = p;
+    }
+  else
+    value_string = STRINGIFY_BIGINT (value, valbuf);
+
+  if (substitute)
+    {
+      int negsub;
+      uintmax_t sub = substitute (&negsub) & maxval;
+      /* NOTE: This is one of the few places where GNU_FORMAT differs from
+        OLDGNU_FORMAT.  The actual differences are:
+
+        1. In OLDGNU_FORMAT all strings in a tar header end in \0
+        2. Incremental archives use oldgnu_header.
+
+        Apart from this they are completely identical. */
+      uintmax_t s = (negsub &= archive_format == GNU_FORMAT) ? - sub : sub;
+      char subbuf[UINTMAX_STRSIZE_BOUND + 1];
+      char *sub_string = STRINGIFY_BIGINT (s, subbuf + 1);
+      if (negsub)
+       *--sub_string = '-';
+      WARN ((0, 0, _("value %s out of %s range %s..%s; substituting %s"),
+            value_string, type, minval_string, maxval_string,
+            sub_string));
+      return to_chars (negsub, s, valsize, 0, where, size, type);
+    }
+  else
+    ERROR ((0, 0, _("value %s out of %s range %s..%s"),
+           value_string, type, minval_string, maxval_string));
+  return false;
+}
+
 /* Convert NEGATIVE VALUE (which was originally of size VALSIZE) to
    external form, using SUBSTITUTE (...) if VALUE won't fit.  Output
    to buffer WHERE with size SIZE.  NEGATIVE is 1 iff VALUE was
 /* Convert NEGATIVE VALUE (which was originally of size VALSIZE) to
    external form, using SUBSTITUTE (...) if VALUE won't fit.  Output
    to buffer WHERE with size SIZE.  NEGATIVE is 1 iff VALUE was
@@ -123,103 +283,61 @@ to_base256 (int negative, uintmax_t value, char *where, size_t size)
    SUBSTITUTE the address of an 0-or-1 flag recording whether the
    substitute value is negative.  */
 
    SUBSTITUTE the address of an 0-or-1 flag recording whether the
    substitute value is negative.  */
 
-static void
+static bool
 to_chars (int negative, uintmax_t value, size_t valsize,
          uintmax_t (*substitute) (int *),
          char *where, size_t size, const char *type)
 {
 to_chars (int negative, uintmax_t value, size_t valsize,
          uintmax_t (*substitute) (int *),
          char *where, size_t size, const char *type)
 {
-  int base256_allowed = (archive_format == GNU_FORMAT
-                        || archive_format == OLDGNU_FORMAT);
+  int gnu_format = (archive_format == GNU_FORMAT
+                   || archive_format == OLDGNU_FORMAT);
 
   /* Generate the POSIX octal representation if the number fits.  */
   if (! negative && value <= MAX_VAL_WITH_DIGITS (size - 1, LG_8))
     {
       where[size - 1] = '\0';
       to_octal (value, where, size - 1);
 
   /* Generate the POSIX octal representation if the number fits.  */
   if (! negative && value <= MAX_VAL_WITH_DIGITS (size - 1, LG_8))
     {
       where[size - 1] = '\0';
       to_octal (value, where, size - 1);
+      return true;
     }
     }
-
-  /* Otherwise, generate the base-256 representation if we are
-     generating an old or new GNU format and if the number fits.  */
-  else if (((negative ? -1 - value : value)
-           <= MAX_VAL_WITH_DIGITS (size - 1, LG_256))
-          && base256_allowed)
+  else if (gnu_format)
     {
     {
-      where[0] = negative ? -1 : 1 << (LG_256 - 1);
-      to_base256 (negative, value, where + 1, size - 1);
-    }
+      /* Try to cope with the number by using traditional GNU format
+        methods */
 
 
-  /* Otherwise, if the number is negative, and if it would not cause
-     ambiguity on this host by confusing positive with negative
-     values, then generate the POSIX octal representation of the value
-     modulo 2**(field bits).  The resulting tar file is
-     machine-dependent, since it depends on the host word size.  Yuck!
-     But this is the traditional behavior.  */
-  else if (negative && valsize * CHAR_BIT <= (size - 1) * LG_8)
-    {
-      static int warned_once;
-      if (! warned_once)
-       {
-         warned_once = 1;
-         WARN ((0, 0, _("Generating negative octal headers")));
-       }
-      where[size - 1] = '\0';
-      to_octal (value & MAX_VAL_WITH_DIGITS (valsize * CHAR_BIT, 1),
-               where, size - 1);
-    }
-
-  /* Otherwise, output a substitute value if possible (with a
-     warning), and an error message if not.  */
-  else
-    {
-      uintmax_t maxval = (base256_allowed
-                         ? MAX_VAL_WITH_DIGITS (size - 1, LG_256)
-                         : MAX_VAL_WITH_DIGITS (size - 1, LG_8));
-      char valbuf[UINTMAX_STRSIZE_BOUND + 1];
-      char maxbuf[UINTMAX_STRSIZE_BOUND];
-      char minbuf[UINTMAX_STRSIZE_BOUND + 1];
-      char const *minval_string;
-      char const *maxval_string = STRINGIFY_BIGINT (maxval, maxbuf);
-      char const *value_string;
-
-      if (base256_allowed)
-       {
-         uintmax_t m = maxval + 1 ? maxval + 1 : maxval / 2 + 1;
-         char *p = STRINGIFY_BIGINT (m, minbuf + 1);
-         *--p = '-';
-         minval_string = p;
-       }
-      else
-       minval_string = "0";
-
-      if (negative)
+      /* Generate the base-256 representation if the number fits.  */
+      if (((negative ? -1 - value : value)
+          <= MAX_VAL_WITH_DIGITS (size - 1, LG_256)))
        {
        {
-         char *p = STRINGIFY_BIGINT (- value, valbuf + 1);
-         *--p = '-';
-         value_string = p;
+         where[0] = negative ? -1 : 1 << (LG_256 - 1);
+         to_base256 (negative, value, where + 1, size - 1);
+         return true;
        }
        }
-      else
-       value_string = STRINGIFY_BIGINT (value, valbuf);
 
 
-      if (substitute)
+      /* Otherwise, if the number is negative, and if it would not cause
+        ambiguity on this host by confusing positive with negative
+        values, then generate the POSIX octal representation of the value
+        modulo 2**(field bits).  The resulting tar file is
+        machine-dependent, since it depends on the host word size.  Yuck!
+        But this is the traditional behavior.  */
+      else if (negative && valsize * CHAR_BIT <= (size - 1) * LG_8)
        {
        {
-         int negsub;
-         uintmax_t sub = substitute (&negsub) & maxval;
-         /* FIXME: This is the only place where GNU_FORMAT differs from
-             OLDGNU_FORMAT. Apart from this they are completely identical. */
-         uintmax_t s = (negsub &= archive_format == GNU_FORMAT) ? - sub : sub;
-         char subbuf[UINTMAX_STRSIZE_BOUND + 1];
-         char *sub_string = STRINGIFY_BIGINT (s, subbuf + 1);
-         if (negsub)
-           *--sub_string = '-';
-         WARN ((0, 0, _("value %s out of %s range %s..%s; substituting %s"),
-                value_string, type, minval_string, maxval_string,
-                sub_string));
-         to_chars (negsub, s, valsize, 0, where, size, type);
+         static int warned_once;
+         if (! warned_once)
+           {
+             warned_once = 1;
+             WARN ((0, 0, _("Generating negative octal headers")));
+           }
+         where[size - 1] = '\0';
+         to_octal (value & MAX_VAL_WITH_DIGITS (valsize * CHAR_BIT, 1),
+                   where, size - 1);
+         return true;
        }
        }
-      else
-       ERROR ((0, 0, _("value %s out of %s range %s..%s"),
-               value_string, type, minval_string, maxval_string));
+      /* Otherwise fall back to substitution, if possible: */
     }
     }
+  else
+    substitute = NULL; /* No substitution for formats, other than GNU */
+
+  return to_chars_subst (negative, gnu_format, value, valsize, substitute,
+                        where, size, type);
 }
 
 static uintmax_t
 }
 
 static uintmax_t
@@ -238,25 +356,25 @@ gid_substitute (int *negative)
   return r;
 }
 
   return r;
 }
 
-void
+static bool
 gid_to_chars (gid_t v, char *p, size_t s)
 {
 gid_to_chars (gid_t v, char *p, size_t s)
 {
-  to_chars (v < 0, (uintmax_t) v, sizeof v, gid_substitute, p, s, "gid_t");
+  return to_chars (v < 0, (uintmax_t) v, sizeof v, gid_substitute, p, s, "gid_t");
 }
 
 }
 
-void
+static bool
 major_to_chars (major_t v, char *p, size_t s)
 {
 major_to_chars (major_t v, char *p, size_t s)
 {
-  to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "major_t");
+  return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "major_t");
 }
 
 }
 
-void
+static bool
 minor_to_chars (minor_t v, char *p, size_t s)
 {
 minor_to_chars (minor_t v, char *p, size_t s)
 {
-  to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "minor_t");
+  return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "minor_t");
 }
 
 }
 
-void
+static bool
 mode_to_chars (mode_t v, char *p, size_t s)
 {
   /* In the common case where the internal and external mode bits are the same,
 mode_to_chars (mode_t v, char *p, size_t s)
 {
   /* In the common case where the internal and external mode bits are the same,
@@ -293,25 +411,19 @@ mode_to_chars (mode_t v, char *p, size_t s)
           | (v & S_IWOTH ? TOWRITE : 0)
           | (v & S_IXOTH ? TOEXEC : 0));
     }
           | (v & S_IWOTH ? TOWRITE : 0)
           | (v & S_IXOTH ? TOEXEC : 0));
     }
-  to_chars (negative, u, sizeof v, 0, p, s, "mode_t");
+  return to_chars (negative, u, sizeof v, 0, p, s, "mode_t");
 }
 
 }
 
-void
+bool
 off_to_chars (off_t v, char *p, size_t s)
 {
 off_to_chars (off_t v, char *p, size_t s)
 {
-  to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "off_t");
+  return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "off_t");
 }
 
 }
 
-void
-size_to_chars (size_t v, char *p, size_t s)
-{
-  to_chars (0, (uintmax_t) v, sizeof v, 0, p, s, "size_t");
-}
-
-void
+bool
 time_to_chars (time_t v, char *p, size_t s)
 {
 time_to_chars (time_t v, char *p, size_t s)
 {
-  to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "time_t");
+  return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "time_t");
 }
 
 static uintmax_t
 }
 
 static uintmax_t
@@ -330,19 +442,19 @@ uid_substitute (int *negative)
   return r;
 }
 
   return r;
 }
 
-void
+static bool
 uid_to_chars (uid_t v, char *p, size_t s)
 {
 uid_to_chars (uid_t v, char *p, size_t s)
 {
-  to_chars (v < 0, (uintmax_t) v, sizeof v, uid_substitute, p, s, "uid_t");
+  return to_chars (v < 0, (uintmax_t) v, sizeof v, uid_substitute, p, s, "uid_t");
 }
 
 }
 
-void
+static bool
 uintmax_to_chars (uintmax_t v, char *p, size_t s)
 {
 uintmax_to_chars (uintmax_t v, char *p, size_t s)
 {
-  to_chars (0, v, sizeof v, 0, p, s, "uintmax_t");
+  return to_chars (0, v, sizeof v, 0, p, s, "uintmax_t");
 }
 
 }
 
-void
+static void
 string_to_chars (char const *str, char *p, size_t s)
 {
   tar_copy_str (p, str, s);
 string_to_chars (char const *str, char *p, size_t s)
 {
   tar_copy_str (p, str, s);
@@ -350,20 +462,25 @@ string_to_chars (char const *str, char *p, size_t s)
 }
 
 \f
 }
 
 \f
-/* A file is considered dumpable if it is sparse and both --sparse and --totals
+/* A directory is always considered dumpable.
+   Otherwise, only regular and contiguous files are considered dumpable.
+   Such a file is dumpable if it is sparse and both --sparse and --totals
    are specified.
    Otherwise, it is dumpable unless any of the following conditions occur:
    are specified.
    Otherwise, it is dumpable unless any of the following conditions occur:
-   
+
    a) it is empty *and* world-readable, or
    b) current archive is /dev/null */
 
    a) it is empty *and* world-readable, or
    b) current archive is /dev/null */
 
-bool
-file_dumpable_p (struct tar_stat_info *st)
+static bool
+file_dumpable_p (struct stat const *st)
 {
 {
+  if (S_ISDIR (st->st_mode))
+    return true;
+  if (! (S_ISREG (st->st_mode) || S_ISCTG (st->st_mode)))
+    return false;
   if (dev_null_output)
   if (dev_null_output)
-    return totals_option && sparse_option && sparse_file_p (st);
-  return !(st->archive_file_size == 0
-          && (st->stat.st_mode & MODE_R) == MODE_R);
+    return totals_option && sparse_option && ST_IS_SPARSE (*st);
+  return ! (st->st_size == 0 && (st->st_mode & MODE_R) == MODE_R);
 }
 
 \f
 }
 
 \f
@@ -385,9 +502,8 @@ write_eot (void)
 
 /* Write a "private" header */
 union block *
 
 /* Write a "private" header */
 union block *
-start_private_header (const char *name, size_t size)
+start_private_header (const char *name, size_t size, time_t t)
 {
 {
-  time_t t;
   union block *header = find_next_block ();
 
   memset (header->buffer, 0, sizeof (union block));
   union block *header = find_next_block ();
 
   memset (header->buffer, 0, sizeof (union block));
@@ -395,7 +511,6 @@ start_private_header (const char *name, size_t size)
   tar_name_copy_str (header->header.name, name, NAME_FIELD_SIZE);
   OFF_TO_CHARS (size, header->header.size);
 
   tar_name_copy_str (header->header.name, name, NAME_FIELD_SIZE);
   OFF_TO_CHARS (size, header->header.size);
 
-  time (&t);
   TIME_TO_CHARS (t, header->header.mtime);
   MODE_TO_CHARS (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, header->header.mode);
   UID_TO_CHARS (getuid (), header->header.uid);
   TIME_TO_CHARS (t, header->header.mtime);
   MODE_TO_CHARS (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, header->header.mode);
   UID_TO_CHARS (getuid (), header->header.uid);
@@ -433,13 +548,13 @@ write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
   union block *header;
   char *tmpname;
 
   union block *header;
   char *tmpname;
 
-  header = start_private_header ("././@LongLink", size);
-  FILL(header->header.mtime, '0');
-  FILL(header->header.mode, '0');
-  FILL(header->header.uid, '0');
-  FILL(header->header.gid, '0');
-  FILL(header->header.devmajor, 0);
-  FILL(header->header.devminor, 0);
+  header = start_private_header ("././@LongLink", size, time (NULL));
+  FILL (header->header.mtime, '0');
+  FILL (header->header.mode, '0');
+  FILL (header->header.uid, '0');
+  FILL (header->header.gid, '0');
+  FILL (header->header.devmajor, 0);
+  FILL (header->header.devminor, 0);
   uid_to_uname (0, &tmpname);
   UNAME_TO_CHARS (tmpname, header->header.uname);
   free (tmpname);
   uid_to_uname (0, &tmpname);
   UNAME_TO_CHARS (tmpname, header->header.uname);
   free (tmpname);
@@ -474,8 +589,10 @@ split_long_name (const char *name, size_t length)
 {
   size_t i;
 
 {
   size_t i;
 
-  if (length > PREFIX_FIELD_SIZE)
-    length = PREFIX_FIELD_SIZE+2;
+  if (length > PREFIX_FIELD_SIZE + 1)
+    length = PREFIX_FIELD_SIZE + 1;
+  else if (ISSLASH (name[length - 1]))
+    length--;
   for (i = length - 1; i > 0; i--)
     if (ISSLASH (name[i]))
       break;
   for (i = length - 1; i > 0; i--)
     if (ISSLASH (name[i]))
       break;
@@ -486,7 +603,7 @@ static union block *
 write_ustar_long_name (const char *name)
 {
   size_t length = strlen (name);
 write_ustar_long_name (const char *name)
 {
   size_t length = strlen (name);
-  size_t i;
+  size_t i, nlen;
   union block *header;
 
   if (length > PREFIX_FIELD_SIZE + NAME_FIELD_SIZE + 1)
   union block *header;
 
   if (length > PREFIX_FIELD_SIZE + NAME_FIELD_SIZE + 1)
@@ -498,7 +615,7 @@ write_ustar_long_name (const char *name)
     }
 
   i = split_long_name (name, length);
     }
 
   i = split_long_name (name, length);
-  if (i == 0 || length - i - 1 > NAME_FIELD_SIZE)
+  if (i == 0 || (nlen = length - i - 1) > NAME_FIELD_SIZE || nlen == 0)
     {
       ERROR ((0, 0,
              _("%s: file name is too long (cannot be split); not dumped"),
     {
       ERROR ((0, 0,
              _("%s: file name is too long (cannot be split); not dumped"),
@@ -576,19 +693,32 @@ write_long_name (struct tar_stat_info *st)
   return write_short_name (st);
 }
 
   return write_short_name (st);
 }
 
-static union block *
-write_extended (struct tar_stat_info *st, union block *old_header)
+union block *
+write_extended (bool global, struct tar_stat_info *st, union block *old_header)
 {
   union block *header, hp;
   char *p;
 {
   union block *header, hp;
   char *p;
+  int type;
+  time_t t;
 
 
-  if (extended_header.buffer || extended_header.stk == NULL)
+  if (st->xhdr.buffer || st->xhdr.stk == NULL)
     return old_header;
 
     return old_header;
 
-  xheader_finish (&extended_header);
+  xheader_finish (&st->xhdr);
   memcpy (hp.buffer, old_header, sizeof (hp));
   memcpy (hp.buffer, old_header, sizeof (hp));
-  p = xheader_xhdr_name (st);
-  xheader_write (XHDTYPE, p, &extended_header);
+  if (global)
+    {
+      type = XGLTYPE;
+      p = xheader_ghdr_name ();
+      time (&t);
+    }
+  else
+    {
+      type = XHDTYPE;
+      p = xheader_xhdr_name (st);
+      t = st->stat.st_mtime;
+    }
+  xheader_write (type, p, t, &st->xhdr);
   free (p);
   header = find_next_block ();
   memcpy (header, &hp.buffer, sizeof (hp.buffer));
   free (p);
   header = find_next_block ();
   memcpy (header, &hp.buffer, sizeof (hp.buffer));
@@ -634,7 +764,8 @@ start_header (struct tar_stat_info *st)
   if (mode_option)
     st->stat.st_mode =
       ((st->stat.st_mode & ~MODE_ALL)
   if (mode_option)
     st->stat.st_mode =
       ((st->stat.st_mode & ~MODE_ALL)
-       | mode_adjust (st->stat.st_mode, mode_option, initial_umask));
+       | mode_adjust (st->stat.st_mode, S_ISDIR (st->stat.st_mode) != 0,
+                     initial_umask, mode_option, NULL));
 
   /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a)
      for a few tars and came up with the following interoperability
 
   /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a)
      for a few tars and came up with the following interoperability
@@ -672,7 +803,8 @@ start_header (struct tar_stat_info *st)
        xheader_store ("uid", st, NULL);
        uid = 0;
       }
        xheader_store ("uid", st, NULL);
        uid = 0;
       }
-    UID_TO_CHARS (uid, header->header.uid);
+    if (!UID_TO_CHARS (uid, header->header.uid))
+      return NULL;
   }
 
   {
   }
 
   {
@@ -683,7 +815,8 @@ start_header (struct tar_stat_info *st)
        xheader_store ("gid", st, NULL);
        gid = 0;
       }
        xheader_store ("gid", st, NULL);
        gid = 0;
       }
-    GID_TO_CHARS (gid, header->header.gid);
+    if (!GID_TO_CHARS (gid, header->header.gid))
+      return NULL;
   }
 
   {
   }
 
   {
@@ -694,20 +827,22 @@ start_header (struct tar_stat_info *st)
        xheader_store ("size", st, NULL);
        size = 0;
       }
        xheader_store ("size", st, NULL);
        size = 0;
       }
-    OFF_TO_CHARS (size, header->header.size);
+    if (!OFF_TO_CHARS (size, header->header.size))
+      return NULL;
   }
 
   {
   }
 
   {
-    struct timespec mtime = get_stat_mtime (&st->stat);
+    struct timespec mtime = set_mtime_option ? mtime_option : st->mtime;
     if (archive_format == POSIX_FORMAT)
       {
        if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec
            || mtime.tv_nsec != 0)
     if (archive_format == POSIX_FORMAT)
       {
        if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec
            || mtime.tv_nsec != 0)
-         xheader_store ("mtime", st, NULL);
+         xheader_store ("mtime", st, &mtime);
        if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec)
          mtime.tv_sec = 0;
       }
        if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec)
          mtime.tv_sec = 0;
       }
-    TIME_TO_CHARS (mtime.tv_sec, header->header.mtime);
+    if (!TIME_TO_CHARS (mtime.tv_sec, header->header.mtime))
+      return NULL;
   }
 
   /* FIXME */
   }
 
   /* FIXME */
@@ -723,7 +858,8 @@ start_header (struct tar_stat_info *st)
          xheader_store ("devmajor", st, NULL);
          devmajor = 0;
        }
          xheader_store ("devmajor", st, NULL);
          devmajor = 0;
        }
-      MAJOR_TO_CHARS (devmajor, header->header.devmajor);
+      if (!MAJOR_TO_CHARS (devmajor, header->header.devmajor))
+       return NULL;
 
       if (archive_format == POSIX_FORMAT
          && MAX_OCTAL_VAL (header->header.devminor) < devminor)
 
       if (archive_format == POSIX_FORMAT
          && MAX_OCTAL_VAL (header->header.devminor) < devminor)
@@ -731,12 +867,14 @@ start_header (struct tar_stat_info *st)
          xheader_store ("devminor", st, NULL);
          devminor = 0;
        }
          xheader_store ("devminor", st, NULL);
          devminor = 0;
        }
-      MINOR_TO_CHARS (devminor, header->header.devminor);
+      if (!MINOR_TO_CHARS (devminor, header->header.devminor))
+       return NULL;
     }
   else if (archive_format != GNU_FORMAT && archive_format != OLDGNU_FORMAT)
     {
     }
   else if (archive_format != GNU_FORMAT && archive_format != OLDGNU_FORMAT)
     {
-      MAJOR_TO_CHARS (0, header->header.devmajor);
-      MINOR_TO_CHARS (0, header->header.devminor);
+      if (!(MAJOR_TO_CHARS (0, header->header.devmajor)
+           && MINOR_TO_CHARS (0, header->header.devminor)))
+       return NULL;
     }
 
   if (archive_format == POSIX_FORMAT)
     }
 
   if (archive_format == POSIX_FORMAT)
@@ -747,8 +885,8 @@ start_header (struct tar_stat_info *st)
   else if (incremental_option)
     if (archive_format == OLDGNU_FORMAT || archive_format == GNU_FORMAT)
       {
   else if (incremental_option)
     if (archive_format == OLDGNU_FORMAT || archive_format == GNU_FORMAT)
       {
-       TIME_TO_CHARS (st->stat.st_atime, header->oldgnu_header.atime);
-       TIME_TO_CHARS (st->stat.st_ctime, header->oldgnu_header.ctime);
+       TIME_TO_CHARS (st->atime.tv_sec, header->oldgnu_header.atime);
+       TIME_TO_CHARS (st->ctime.tv_sec, header->oldgnu_header.ctime);
       }
 
   header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
       }
 
   header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
@@ -845,14 +983,12 @@ finish_header (struct tar_stat_info *st,
       && header->header.typeflag != XHDTYPE
       && header->header.typeflag != XGLTYPE)
     {
       && header->header.typeflag != XHDTYPE
       && header->header.typeflag != XGLTYPE)
     {
-      /* These globals are parameters to print_header, sigh.  */
-
-      current_header = header;
+      /* FIXME: This global is used in print_header, sigh.  */
       current_format = archive_format;
       current_format = archive_format;
-      print_header (st, block_ordinal);
+      print_header (st, header, block_ordinal);
     }
 
     }
 
-  header = write_extended (st, header);
+  header = write_extended (false, st, header);
   simple_finish_header (header);
 }
 \f
   simple_finish_header (header);
 }
 \f
@@ -863,7 +999,6 @@ pad_archive (off_t size_left)
   union block *blk;
   while (size_left > 0)
     {
   union block *blk;
   while (size_left > 0)
     {
-      save_sizeleft = size_left;
       blk = find_next_block ();
       memset (blk->buffer, 0, BLOCKSIZE);
       set_next_block_after (blk);
       blk = find_next_block ();
       memset (blk->buffer, 0, BLOCKSIZE);
       set_next_block_after (blk);
@@ -889,16 +1024,11 @@ dump_regular_file (int fd, struct tar_stat_info *st)
 
   finish_header (st, blk, block_ordinal);
 
 
   finish_header (st, blk, block_ordinal);
 
+  mv_begin_write (st->file_name, st->stat.st_size, st->stat.st_size);
   while (size_left > 0)
     {
       size_t bufsize, count;
 
   while (size_left > 0)
     {
       size_t bufsize, count;
 
-      if (multi_volume_option)
-       {
-         assign_string (&save_name, st->orig_file_name);
-         save_sizeleft = size_left;
-         save_totsize = st->stat.st_size;
-       }
       blk = find_next_block ();
 
       bufsize = available_space_after (blk);
       blk = find_next_block ();
 
       bufsize = available_space_after (blk);
@@ -912,7 +1042,7 @@ dump_regular_file (int fd, struct tar_stat_info *st)
            memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
        }
 
            memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
        }
 
-      count = (fd < 0) ? bufsize : safe_read (fd, blk->buffer, bufsize);
+      count = (fd <= 0) ? bufsize : safe_read (fd, blk->buffer, bufsize);
       if (count == SAFE_READ_ERROR)
        {
          read_diag_details (st->orig_file_name,
       if (count == SAFE_READ_ERROR)
        {
          read_diag_details (st->orig_file_name,
@@ -921,125 +1051,64 @@ dump_regular_file (int fd, struct tar_stat_info *st)
          return dump_status_short;
        }
       size_left -= count;
          return dump_status_short;
        }
       size_left -= count;
-      if (count)
-       set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
+      set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
 
       if (count != bufsize)
        {
          char buf[UINTMAX_STRSIZE_BOUND];
          memset (blk->buffer + count, 0, bufsize - count);
 
       if (count != bufsize)
        {
          char buf[UINTMAX_STRSIZE_BOUND];
          memset (blk->buffer + count, 0, bufsize - count);
-         WARN ((0, 0,
-                ngettext ("%s: File shrank by %s byte; padding with zeros",
-                          "%s: File shrank by %s bytes; padding with zeros",
-                          size_left),
-                quotearg_colon (st->orig_file_name),
-                STRINGIFY_BIGINT (size_left, buf)));
+         WARNOPT (WARN_FILE_SHRANK,
+                  (0, 0,
+                   ngettext ("%s: File shrank by %s byte; padding with zeros",
+                             "%s: File shrank by %s bytes; padding with zeros",
+                             size_left),
+                   quotearg_colon (st->orig_file_name),
+                   STRINGIFY_BIGINT (size_left, buf)));
          if (! ignore_failed_read_option)
          if (! ignore_failed_read_option)
-           exit_status = TAREXIT_FAILURE;
-         pad_archive (size_left - (bufsize-count));
+           set_exit_status (TAREXIT_DIFFERS);
+         pad_archive (size_left - (bufsize - count));
          return dump_status_short;
        }
     }
   return dump_status_ok;
 }
 
          return dump_status_short;
        }
     }
   return dump_status_ok;
 }
 
-static void
-dump_regular_finish (int fd, struct tar_stat_info *st,
-                    struct timespec original_ctime)
-{
-  if (fd >= 0)
-    {
-      struct stat final_stat;
-      if (fstat (fd, &final_stat) != 0)
-       {
-         stat_diag (st->orig_file_name);
-       }
-      else if (final_stat.st_ctime != original_ctime.tv_sec
-              || (get_stat_ctime (&final_stat).tv_nsec
-                  != original_ctime.tv_nsec))
-       {
-         WARN ((0, 0, _("%s: file changed as we read it"),
-                quotearg_colon (st->orig_file_name)));
-       }
-      if (close (fd) != 0)
-       {
-         close_diag (st->orig_file_name);
-       }
-    }
-  if (remove_files_option)
-    {
-      if (unlink (st->orig_file_name) == -1)
-       unlink_error (st->orig_file_name);
-    }
-}
-
-/* Look in directory DIRNAME for a cache directory tag file
-   with the magic name "CACHEDIR.TAG" and a standard header,
-   as described at:
-       http://www.brynosaurus.com/cachedir
-   Applications can write this file into directories they create
-   for use as caches containing purely regenerable, non-precious data,
-   allowing us to avoid archiving them if --exclude-caches is specified. */
-
-#define CACHEDIR_SIGNATURE "Signature: 8a477f597d28d172789f06886806bc55"
-#define CACHEDIR_SIGNATURE_SIZE (sizeof CACHEDIR_SIGNATURE - 1)
-
-static bool
-check_cache_directory (char *dirname)
-{
-  static char tagname[] = "CACHEDIR.TAG";
-  char *tagpath;
-  int fd;
-  int tag_present = false;
-
-  tagpath = xmalloc (strlen (dirname) + strlen (tagname) + 1);
-  strcpy (tagpath, dirname);
-  strcat (tagpath, tagname);
-
-  fd = open (tagpath, O_RDONLY);
-  if (fd >= 0)
-    {
-      static char tagbuf[CACHEDIR_SIGNATURE_SIZE];
-
-      if (read (fd, tagbuf, CACHEDIR_SIGNATURE_SIZE)
-         == CACHEDIR_SIGNATURE_SIZE
-         && memcmp (tagbuf, CACHEDIR_SIGNATURE, CACHEDIR_SIGNATURE_SIZE) == 0)
-       tag_present = true;
-
-      close (fd);
-    }
-
-  free (tagpath);
-
-  return tag_present;
-}
+\f
+/* Copy info from the directory identified by ST into the archive.
+   DIRECTORY contains the directory's entries.  */
 
 static void
 
 static void
-dump_dir0 (char *directory,
-          struct tar_stat_info *st, int top_level, dev_t parent_device)
+dump_dir0 (struct tar_stat_info *st, char const *directory)
 {
 {
-  dev_t our_device = st->stat.st_dev;
+  bool top_level = ! st->parent;
+  const char *tag_file_name;
+  union block *blk = NULL;
+  off_t block_ordinal = current_block_ordinal ();
 
 
-  if (!is_avoided_name (st->orig_file_name))
-    {
-      union block *blk = NULL;
-      off_t block_ordinal = current_block_ordinal ();
-      st->stat.st_size = 0;    /* force 0 size on dir */
+  st->stat.st_size = 0;        /* force 0 size on dir */
 
 
-      blk = start_header (st);
-      if (!blk)
-       return;
+  blk = start_header (st);
+  if (!blk)
+    return;
 
 
-      if (incremental_option)
-       blk->header.typeflag = GNUTYPE_DUMPDIR;
-      else /* if (standard_option) */
-       blk->header.typeflag = DIRTYPE;
+  if (incremental_option && archive_format != POSIX_FORMAT)
+    blk->header.typeflag = GNUTYPE_DUMPDIR;
+  else /* if (standard_option) */
+    blk->header.typeflag = DIRTYPE;
 
 
-      /* If we're gnudumping, we aren't done yet so don't close it.  */
+  /* If we're gnudumping, we aren't done yet so don't close it.  */
 
 
-      if (!incremental_option)
-       finish_header (st, blk, block_ordinal);
-      else if (gnu_list_name->dir_contents)
+  if (!incremental_option)
+    finish_header (st, blk, block_ordinal);
+  else if (gnu_list_name->directory)
+    {
+      if (archive_format == POSIX_FORMAT)
+       {
+         xheader_store ("GNU.dumpdir", st,
+                        safe_directory_contents (gnu_list_name->directory));
+         finish_header (st, blk, block_ordinal);
+       }
+      else
        {
          off_t size_left;
          off_t totsize;
        {
          off_t size_left;
          off_t totsize;
@@ -1048,28 +1117,16 @@ dump_dir0 (char *directory,
          const char *buffer, *p_buffer;
 
          block_ordinal = current_block_ordinal ();
          const char *buffer, *p_buffer;
 
          block_ordinal = current_block_ordinal ();
-         buffer = gnu_list_name->dir_contents; /* FOO */
-         totsize = 0;
-         if (buffer)
-           for (p_buffer = buffer; *p_buffer; )
-             {
-               size_t size = strlen (p_buffer) + 1;
-               totsize += size;
-               p_buffer += size;
-             }
-         totsize++;
+         buffer = safe_directory_contents (gnu_list_name->directory);
+         totsize = dumpdir_size (buffer);
          OFF_TO_CHARS (totsize, blk->header.size);
          finish_header (st, blk, block_ordinal);
          p_buffer = buffer;
          size_left = totsize;
          OFF_TO_CHARS (totsize, blk->header.size);
          finish_header (st, blk, block_ordinal);
          p_buffer = buffer;
          size_left = totsize;
+
+         mv_begin_write (st->file_name, totsize, totsize);
          while (size_left > 0)
            {
          while (size_left > 0)
            {
-             if (multi_volume_option)
-               {
-                 assign_string (&save_name, st->orig_file_name);
-                 save_sizeleft = size_left;
-                 save_totsize = totsize;
-               }
              blk = find_next_block ();
              bufsize = available_space_after (blk);
              if (size_left < bufsize)
              blk = find_next_block ();
              bufsize = available_space_after (blk);
              if (size_left < bufsize)
@@ -1084,10 +1141,8 @@ dump_dir0 (char *directory,
              p_buffer += bufsize;
              set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
            }
              p_buffer += bufsize;
              set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
            }
-         if (multi_volume_option)
-           assign_string (&save_name, 0);
-         return;
        }
        }
+      return;
     }
 
   if (!recursion_option)
     }
 
   if (!recursion_option)
@@ -1095,50 +1150,69 @@ dump_dir0 (char *directory,
 
   if (one_file_system_option
       && !top_level
 
   if (one_file_system_option
       && !top_level
-      && parent_device != st->stat.st_dev)
+      && st->parent->stat.st_dev != st->stat.st_dev)
     {
       if (verbose_option)
     {
       if (verbose_option)
-       WARN ((0, 0,
-              _("%s: file is on a different filesystem; not dumped"),
-              quotearg_colon (st->orig_file_name)));
-      return;
+       WARNOPT (WARN_XDEV,
+                (0, 0,
+                 _("%s: file is on a different filesystem; not dumped"),
+                 quotearg_colon (st->orig_file_name)));
     }
     }
-
-  if (exclude_caches_option
-      && check_cache_directory(st->orig_file_name))
+  else
     {
     {
-      if (verbose_option)
-       WARN ((0, 0,
-              _("%s: contains a cache directory tag; not dumped"),
-              quotearg_colon (st->orig_file_name)));
-      return;
-    }
+      char *name_buf;
+      size_t name_size;
 
 
-  {
-    char const *entry;
-    size_t entry_len;
-    char *name_buf = xstrdup (st->orig_file_name);
-    size_t name_size = strlen (name_buf);
-    size_t name_len = name_size;
-
-    /* Now output all the files in the directory.  */
-    /* FIXME: Should speed this up by cd-ing into the dir.  */
+      switch (check_exclusion_tags (st, &tag_file_name))
+       {
+       case exclusion_tag_all:
+         /* Handled in dump_file0 */
+         break;
 
 
-    for (entry = directory; (entry_len = strlen (entry)) != 0;
-        entry += entry_len + 1)
-      {
-       if (name_size < name_len + entry_len)
+       case exclusion_tag_none:
          {
          {
-           name_size = name_len + entry_len;
-           name_buf = xrealloc (name_buf, name_size + 1);
-         }
-       strcpy (name_buf + name_len, entry);
-       if (!excluded_name (name_buf))
-         dump_file (name_buf, 0, our_device);
-      }
+           char const *entry;
+           size_t entry_len;
+           size_t name_len;
 
 
-    free (name_buf);
-  }
+           name_buf = xstrdup (st->orig_file_name);
+           name_size = name_len = strlen (name_buf);
+
+           /* Now output all the files in the directory.  */
+           for (entry = directory; (entry_len = strlen (entry)) != 0;
+                entry += entry_len + 1)
+             {
+               if (name_size < name_len + entry_len)
+                 {
+                   name_size = name_len + entry_len;
+                   name_buf = xrealloc (name_buf, name_size + 1);
+                 }
+               strcpy (name_buf + name_len, entry);
+               if (!excluded_name (name_buf))
+                 dump_file (st, entry, name_buf);
+             }
+
+           free (name_buf);
+         }
+         break;
+
+       case exclusion_tag_contents:
+         exclusion_tag_warning (st->orig_file_name, tag_file_name,
+                                _("contents not dumped"));
+         name_size = strlen (st->orig_file_name) + strlen (tag_file_name) + 1;
+         name_buf = xmalloc (name_size);
+         strcpy (name_buf, st->orig_file_name);
+         strcat (name_buf, tag_file_name);
+         dump_file (st, tag_file_name, name_buf);
+         free (name_buf);
+         break;
+
+       case exclusion_tag_under:
+         exclusion_tag_warning (st->orig_file_name, tag_file_name,
+                                _("contents not dumped"));
+         break;
+       }
+    }
 }
 
 /* Ensure exactly one trailing slash.  */
 }
 
 /* Ensure exactly one trailing slash.  */
@@ -1154,37 +1228,85 @@ ensure_slash (char **pstr)
   (*pstr)[len] = '\0';
 }
 
   (*pstr)[len] = '\0';
 }
 
+/* If we just ran out of file descriptors, release a file descriptor
+   in the directory chain somewhere leading from DIR->parent->parent
+   up through the root.  Return true if successful, false (preserving
+   errno == EMFILE) otherwise.
+
+   Do not release DIR's file descriptor, or DIR's parent, as other
+   code assumes that they work.  On some operating systems, another
+   process can claim file descriptor resources as we release them, and
+   some calls or their emulations require multiple file descriptors,
+   so callers should not give up if a single release doesn't work.  */
+
 static bool
 static bool
-dump_dir (struct tar_stat_info *st, int top_level, dev_t parent_device)
+open_failure_recover (struct tar_stat_info const *dir)
+{
+  if (errno == EMFILE && dir && dir->parent)
+    {
+      struct tar_stat_info *p;
+      for (p = dir->parent->parent; p; p = p->parent)
+       if (0 < p->fd && (! p->parent || p->parent->fd <= 0))
+         {
+           tar_stat_close (p);
+           return true;
+         }
+      errno = EMFILE;
+    }
+
+  return false;
+}
+
+/* Return the directory entries of ST, in a dynamically allocated buffer,
+   each entry followed by '\0' and the last followed by an extra '\0'.
+   Return null on failure, setting errno.  */
+char *
+get_directory_entries (struct tar_stat_info *st)
 {
 {
-  char *directory;
+  while (! (st->dirstream = fdopendir (st->fd)))
+    if (! open_failure_recover (st))
+      return 0;
+  return streamsavedir (st->dirstream);
+}
 
 
-  directory = savedir (st->orig_file_name);
-  if (!directory)
+/* Dump the directory ST.  Return true if successful, false (emitting
+   diagnostics) otherwise.  Get ST's entries, recurse through its
+   subdirectories, and clean up file descriptors afterwards.  */
+static bool
+dump_dir (struct tar_stat_info *st)
+{
+  char *directory = get_directory_entries (st);
+  if (! directory)
     {
       savedir_diag (st->orig_file_name);
       return false;
     }
 
     {
       savedir_diag (st->orig_file_name);
       return false;
     }
 
-  ensure_slash (&st->orig_file_name);
-  ensure_slash (&st->file_name);
-
-  dump_dir0 (directory, st, top_level, parent_device);
+  dump_dir0 (st, directory);
 
 
+  restore_parent_fd (st);
   free (directory);
   return true;
 }
 
   free (directory);
   return true;
 }
 
+\f
+/* Number of links a file can have without having to be entered into
+   the link table.  Typically this is 1, but in trickier circumstances
+   it is 0.  */
+static nlink_t trivial_link_count;
+
 \f
 /* Main functions of this module.  */
 
 void
 create_archive (void)
 {
 \f
 /* Main functions of this module.  */
 
 void
 create_archive (void)
 {
-  char *p;
+  struct name const *p;
+
+  trivial_link_count = name_count <= 1 && ! dereference_option;
 
   open_archive (ACCESS_WRITE);
 
   open_archive (ACCESS_WRITE);
-  xheader_write_global ();
+  buffer_write_global_xheader ();
 
   if (incremental_option)
     {
 
   if (incremental_option)
     {
@@ -1195,30 +1317,48 @@ create_archive (void)
       collect_and_sort_names ();
 
       while ((p = name_from_list ()) != NULL)
       collect_and_sort_names ();
 
       while ((p = name_from_list ()) != NULL)
-       if (!excluded_name (p))
-         dump_file (p, -1, (dev_t) 0);
+       if (!excluded_name (p->name))
+         dump_file (0, p->name, p->name);
 
       blank_name_list ();
       while ((p = name_from_list ()) != NULL)
 
       blank_name_list ();
       while ((p = name_from_list ()) != NULL)
-       if (!excluded_name (p))
+       if (!excluded_name (p->name))
          {
          {
-           size_t plen = strlen (p);
+           struct tar_stat_info st;
+           size_t plen = strlen (p->name);
            if (buffer_size <= plen)
              {
                while ((buffer_size *= 2) <= plen)
                  continue;
                buffer = xrealloc (buffer, buffer_size);
              }
            if (buffer_size <= plen)
              {
                while ((buffer_size *= 2) <= plen)
                  continue;
                buffer = xrealloc (buffer, buffer_size);
              }
-           memcpy (buffer, p, plen);
+           memcpy (buffer, p->name, plen);
            if (! ISSLASH (buffer[plen - 1]))
            if (! ISSLASH (buffer[plen - 1]))
-             buffer[plen++] = '/';
-           q = gnu_list_name->dir_contents;
+             buffer[plen++] = DIRECTORY_SEPARATOR;
+           tar_stat_init (&st);
+           q = directory_contents (gnu_list_name->directory);
            if (q)
              while (*q)
                {
                  size_t qlen = strlen (q);
                  if (*q == 'Y')
                    {
            if (q)
              while (*q)
                {
                  size_t qlen = strlen (q);
                  if (*q == 'Y')
                    {
+                     if (! st.orig_file_name)
+                       {
+                         int fd = open (p->name, open_searchdir_flags);
+                         if (fd < 0)
+                           {
+                             open_diag (p->name);
+                             break;
+                           }
+                         st.fd = fd;
+                         if (fstat (fd, &st.stat) != 0)
+                           {
+                             stat_diag (p->name);
+                             break;
+                           }
+                         st.orig_file_name = xstrdup (p->name);
+                       }
                      if (buffer_size < plen + qlen)
                        {
                          while ((buffer_size *=2 ) < plen + qlen)
                      if (buffer_size < plen + qlen)
                        {
                          while ((buffer_size *=2 ) < plen + qlen)
@@ -1226,23 +1366,25 @@ create_archive (void)
                          buffer = xrealloc (buffer, buffer_size);
                        }
                      strcpy (buffer + plen, q + 1);
                          buffer = xrealloc (buffer, buffer_size);
                        }
                      strcpy (buffer + plen, q + 1);
-                     dump_file (buffer, -1, (dev_t) 0);
+                     dump_file (&st, q + 1, buffer);
                    }
                  q += qlen + 1;
                }
                    }
                  q += qlen + 1;
                }
+           tar_stat_destroy (&st);
          }
       free (buffer);
     }
   else
     {
          }
       free (buffer);
     }
   else
     {
-      while ((p = name_next (1)) != NULL)
-       if (!excluded_name (p))
-         dump_file (p, 1, (dev_t) 0);
+      const char *name;
+      while ((name = name_next (1)) != NULL)
+       if (!excluded_name (name))
+         dump_file (0, name, name);
     }
 
   write_eot ();
   close_archive ();
     }
 
   write_eot ();
   close_archive ();
-
+  finish_deferred_unlinks ();
   if (listed_incremental_option)
     write_directory_file ();
 }
   if (listed_incremental_option)
     write_directory_file ();
 }
@@ -1267,12 +1409,13 @@ compare_links (void const *entry1, void const *entry2)
 }
 
 static void
 }
 
 static void
-unknown_file_error (char *p)
+unknown_file_error (char const *p)
 {
 {
-  WARN ((0, 0, _("%s: Unknown file type; file ignored"),
-        quotearg_colon (p)));
+  WARNOPT (WARN_FILE_IGNORED,
+          (0, 0, _("%s: Unknown file type; file ignored"),
+           quotearg_colon (p)));
   if (!ignore_failed_read_option)
   if (!ignore_failed_read_option)
-    exit_status = TAREXIT_FAILURE;
+    set_exit_status (TAREXIT_FAILURE);
 }
 
 \f
 }
 
 \f
@@ -1283,12 +1426,13 @@ unknown_file_error (char *p)
    again if we've done it once already.  */
 static Hash_table *link_table;
 
    again if we've done it once already.  */
 static Hash_table *link_table;
 
-/* Try to dump stat as a hard link to another file in the archive. If
-   succeeded returns true */
+/* Try to dump stat as a hard link to another file in the archive.
+   Return true if successful.  */
 static bool
 dump_hard_link (struct tar_stat_info *st)
 {
 static bool
 dump_hard_link (struct tar_stat_info *st)
 {
-  if (link_table && st->stat.st_nlink > 1)
+  if (link_table
+      && (trivial_link_count < st->stat.st_nlink || remove_files_option))
     {
       struct link lp;
       struct link *duplicate;
     {
       struct link lp;
       struct link *duplicate;
@@ -1315,14 +1459,14 @@ dump_hard_link (struct tar_stat_info *st)
          st->stat.st_size = 0;
          blk = start_header (st);
          if (!blk)
          st->stat.st_size = 0;
          blk = start_header (st);
          if (!blk)
-           return true;
+           return false;
          tar_copy_str (blk->header.linkname, link_name, NAME_FIELD_SIZE);
 
          blk->header.typeflag = LNKTYPE;
          finish_header (st, blk, block_ordinal);
 
          tar_copy_str (blk->header.linkname, link_name, NAME_FIELD_SIZE);
 
          blk->header.typeflag = LNKTYPE;
          finish_header (st, blk, block_ordinal);
 
-         if (remove_files_option && unlink (st->orig_file_name) != 0)
-           unlink_error (st->orig_file_name);
+         if (remove_files_option)
+           queue_deferred_unlink (st->orig_file_name, false);
 
          return true;
        }
 
          return true;
        }
@@ -1333,15 +1477,24 @@ dump_hard_link (struct tar_stat_info *st)
 static void
 file_count_links (struct tar_stat_info *st)
 {
 static void
 file_count_links (struct tar_stat_info *st)
 {
-  if (st->stat.st_nlink > 1)
+  if (hard_dereference_option)
+    return;
+  if (trivial_link_count < st->stat.st_nlink)
     {
       struct link *duplicate;
     {
       struct link *duplicate;
-      struct link *lp = xmalloc (offsetof (struct link, name)
-                                + strlen (st->orig_file_name) + 1);
+      char *linkname = NULL;
+      struct link *lp;
+
+      assign_string (&linkname, st->orig_file_name);
+      transform_name (&linkname, XFORM_LINK);
+
+      lp = xmalloc (offsetof (struct link, name)
+                                + strlen (linkname) + 1);
       lp->ino = st->stat.st_ino;
       lp->dev = st->stat.st_dev;
       lp->nlink = st->stat.st_nlink;
       lp->ino = st->stat.st_ino;
       lp->dev = st->stat.st_dev;
       lp->nlink = st->stat.st_nlink;
-      strcpy (lp->name, st->orig_file_name);
+      strcpy (lp->name, linkname);
+      free (linkname);
 
       if (! ((link_table
              || (link_table = hash_initialize (0, 0, hash_link,
 
       if (! ((link_table
              || (link_table = hash_initialize (0, 0, hash_link,
@@ -1370,30 +1523,101 @@ check_links (void)
     {
       if (lp->nlink)
        {
     {
       if (lp->nlink)
        {
-         WARN ((0, 0, _("Missing links to %s.\n"), quote (lp->name)));
+         WARN ((0, 0, _("Missing links to %s."), quote (lp->name)));
        }
     }
 }
 
        }
     }
 }
 
+/* Assuming DIR is the working directory, open FILE, using FLAGS to
+   control the open.  A null DIR means to use ".".  If we are low on
+   file descriptors, try to release one or more from DIR's parents to
+   reuse it.  */
+int
+subfile_open (struct tar_stat_info const *dir, char const *file, int flags)
+{
+  int fd;
+
+  static bool initialized;
+  if (! initialized)
+    {
+      /* Initialize any tables that might be needed when file
+        descriptors are exhausted, and whose initialization might
+        require a file descriptor.  This includes the system message
+        catalog and tar's message catalog.  */
+      initialized = true;
+      strerror (ENOENT);
+      gettext ("");
+    }
+
+  while ((fd = openat (dir ? dir->fd : AT_FDCWD, file, flags)) < 0
+        && open_failure_recover (dir))
+    continue;
+  return fd;
+}
+
+/* Restore the file descriptor for ST->parent, if it was temporarily
+   closed to conserve file descriptors.  On failure, set the file
+   descriptor to the negative of the corresponding errno value.  Call
+   this every time a subdirectory is ascended from.  */
+void
+restore_parent_fd (struct tar_stat_info const *st)
+{
+  struct tar_stat_info *parent = st->parent;
+  if (parent && ! parent->fd)
+    {
+      int parentfd = openat (st->fd, "..", open_searchdir_flags);
+      struct stat parentstat;
+
+      if (parentfd < 0)
+       parentfd = - errno;
+      else if (! (fstat (parentfd, &parentstat) == 0
+                 && parent->stat.st_ino == parentstat.st_ino
+                 && parent->stat.st_dev == parentstat.st_dev))
+       {
+         close (parentfd);
+         parentfd = IMPOSTOR_ERRNO;
+       }
+
+      if (parentfd < 0)
+       {
+         int origfd = open (parent->orig_file_name, open_searchdir_flags);
+         if (0 <= origfd)
+           {
+             if (fstat (parentfd, &parentstat) == 0
+                 && parent->stat.st_ino == parentstat.st_ino
+                 && parent->stat.st_dev == parentstat.st_dev)
+               parentfd = origfd;
+             else
+               close (origfd);
+           }
+       }
+
+      parent->fd = parentfd;
+    }
+}
 
 
-/* Dump a single file, recursing on directories.  P is the file name
-   to dump.  TOP_LEVEL tells whether this is a top-level call; zero
-   means no, positive means yes, and negative means the top level
-   of an incremental dump.  PARENT_DEVICE is the device of P's
-   parent directory; it is examined only if TOP_LEVEL is zero. */
+/* Dump a single file, recursing on directories.  ST is the file's
+   status info, NAME its name relative to the parent directory, and P
+   its full name (which may be relative to the working directory).  */
 
 /* FIXME: One should make sure that for *every* path leading to setting
    exit_status to failure, a clear diagnostic has been issued.  */
 
 static void
 
 /* FIXME: One should make sure that for *every* path leading to setting
    exit_status to failure, a clear diagnostic has been issued.  */
 
 static void
-dump_file0 (struct tar_stat_info *st, char *p,
-           int top_level, dev_t parent_device)
+dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
 {
   union block *header;
   char type;
 {
   union block *header;
   char type;
+  off_t original_size;
   struct timespec original_ctime;
   struct timespec restore_times[2];
   off_t block_ordinal = -1;
   struct timespec original_ctime;
   struct timespec restore_times[2];
   off_t block_ordinal = -1;
+  int fd = 0;
+  bool is_dir;
+  struct tar_stat_info const *parent = st->parent;
+  bool top_level = ! parent;
+  int parentfd = top_level ? AT_FDCWD : parent->fd;
+  void (*diag) (char const *) = 0;
 
   if (interactive_option && !confirm ("add", p))
     return;
 
   if (interactive_option && !confirm ("add", p))
     return;
@@ -1402,15 +1626,37 @@ dump_file0 (struct tar_stat_info *st, char *p,
   assign_string (&st->file_name,
                  safer_name_suffix (p, false, absolute_names_option));
 
   assign_string (&st->file_name,
                  safer_name_suffix (p, false, absolute_names_option));
 
-  if (deref_stat (dereference_option, p, &st->stat) != 0)
+  transform_name (&st->file_name, XFORM_REGFILE);
+
+  if (parentfd < 0 && ! top_level)
+    {
+      errno = - parentfd;
+      diag = open_diag;
+    }
+  else if (fstatat (parentfd, name, &st->stat, fstatat_flags) != 0)
+    diag = stat_diag;
+  else if (file_dumpable_p (&st->stat))
     {
     {
-      stat_diag (p);
+      fd = subfile_open (parent, name, open_read_flags);
+      if (fd < 0)
+       diag = open_diag;
+      else
+       {
+         st->fd = fd;
+         if (fstat (fd, &st->stat) != 0)
+           diag = stat_diag;
+       }
+    }
+  if (diag)
+    {
+      file_removed_diag (p, top_level, diag);
       return;
     }
       return;
     }
-  st->archive_file_size = st->stat.st_size;
-  original_ctime = get_stat_ctime (&st->stat);
-  restore_times[0] = get_stat_atime (&st->stat);
-  restore_times[1] = get_stat_mtime (&st->stat);
+
+  st->archive_file_size = original_size = st->stat.st_size;
+  st->atime = restore_times[0] = get_stat_atime (&st->stat);
+  st->mtime = restore_times[1] = get_stat_mtime (&st->stat);
+  st->ctime = original_ctime = get_stat_ctime (&st->stat);
 
 #ifdef S_ISHIDDEN
   if (S_ISHIDDEN (st->stat.st_mode))
 
 #ifdef S_ISHIDDEN
   if (S_ISHIDDEN (st->stat.st_mode))
@@ -1433,64 +1679,58 @@ dump_file0 (struct tar_stat_info *st, char *p,
 
   if (!(incremental_option && !is_individual_file (p))
       && !S_ISDIR (st->stat.st_mode)
 
   if (!(incremental_option && !is_individual_file (p))
       && !S_ISDIR (st->stat.st_mode)
-      && OLDER_STAT_TIME (st->stat, m)
-      && (!after_date_option || OLDER_STAT_TIME (st->stat, c)))
+      && OLDER_TAR_STAT_TIME (*st, m)
+      && (!after_date_option || OLDER_TAR_STAT_TIME (*st, c)))
     {
       if (!incremental_option && verbose_option)
     {
       if (!incremental_option && verbose_option)
-       WARN ((0, 0, _("%s: file is unchanged; not dumped"),
-              quotearg_colon (p)));
+       WARNOPT (WARN_FILE_UNCHANGED,
+                (0, 0, _("%s: file is unchanged; not dumped"),
+                 quotearg_colon (p)));
       return;
     }
 
   /* See if we are trying to dump the archive.  */
   if (sys_file_is_archive (st))
     {
       return;
     }
 
   /* See if we are trying to dump the archive.  */
   if (sys_file_is_archive (st))
     {
-      WARN ((0, 0, _("%s: file is the archive; not dumped"),
-            quotearg_colon (p)));
+      WARNOPT (WARN_IGNORE_ARCHIVE,
+              (0, 0, _("%s: file is the archive; not dumped"),
+               quotearg_colon (p)));
       return;
     }
 
       return;
     }
 
-  if (is_avoided_name (p))
+  is_dir = S_ISDIR (st->stat.st_mode) != 0;
+
+  if (!is_dir && dump_hard_link (st))
     return;
     return;
-  if (S_ISDIR (st->stat.st_mode))
-    {
-      dump_dir (st, top_level, parent_device);
-      if (atime_preserve_option)
-       utimens (p, restore_times);
-      return;
-    }
-  else
-    {
-      /* Check for multiple links.  */
-      if (dump_hard_link (st))
-       return;
 
 
-      /* This is not a link to a previously dumped file, so dump it.  */
+  if (is_dir || S_ISREG (st->stat.st_mode) || S_ISCTG (st->stat.st_mode))
+    {
+      bool ok;
+      struct stat final_stat;
 
 
-      if (S_ISREG (st->stat.st_mode)
-         || S_ISCTG (st->stat.st_mode))
+      if (is_dir)
        {
        {
-         int fd;
-         enum dump_status status;
+         const char *tag_file_name;
+         ensure_slash (&st->orig_file_name);
+         ensure_slash (&st->file_name);
 
 
-         if (file_dumpable_p (st))
+         if (check_exclusion_tags (st, &tag_file_name) == exclusion_tag_all)
            {
            {
-             fd = open (st->orig_file_name,
-                        O_RDONLY | O_BINARY);
-             if (fd < 0)
-               {
-                 if (!top_level && errno == ENOENT)
-                   WARN ((0, 0, _("%s: File removed before we read it"),
-                          quotearg_colon (st->orig_file_name)));
-                 else
-                   open_diag (st->orig_file_name);
-                 return;
-               }
+             exclusion_tag_warning (st->orig_file_name, tag_file_name,
+                                    _("directory not dumped"));
+             return;
            }
            }
-         else
-           fd = -1;
 
 
-         if (fd != -1 && sparse_option && sparse_file_p (st))
+         ok = dump_dir (st);
+
+         fd = st->fd;
+         parentfd = top_level ? AT_FDCWD : parent->fd;
+       }
+      else
+       {
+         enum dump_status status;
+
+         if (fd && sparse_option && ST_IS_SPARSE (st->stat))
            {
              status = sparse_dump_file (fd, st);
              if (status == dump_status_not_implemented)
            {
              status = sparse_dump_file (fd, st);
              if (status == dump_status_not_implemented)
@@ -1502,90 +1742,128 @@ dump_file0 (struct tar_stat_info *st, char *p,
          switch (status)
            {
            case dump_status_ok:
          switch (status)
            {
            case dump_status_ok:
-             if (multi_volume_option)
-               assign_string (&save_name, 0);
-             dump_regular_finish (fd, st, original_ctime);
-             break;
-
            case dump_status_short:
            case dump_status_short:
-             if (multi_volume_option)
-               assign_string (&save_name, 0);
-             close (fd);
+             file_count_links (st);
              break;
 
            case dump_status_fail:
              break;
 
            case dump_status_fail:
-             close (fd);
-             return;
+             break;
 
            case dump_status_not_implemented:
              abort ();
            }
 
 
            case dump_status_not_implemented:
              abort ();
            }
 
-         if (atime_preserve_option)
-           utimens (st->orig_file_name, restore_times);
-         file_count_links (st);
-         return;
+         ok = status == dump_status_ok;
        }
        }
-#ifdef HAVE_READLINK
-      else if (S_ISLNK (st->stat.st_mode))
+
+      if (ok)
        {
        {
-         char *buffer;
-         int size;
-         size_t linklen = st->stat.st_size;
-         if (linklen != st->stat.st_size || linklen + 1 == 0)
-           xalloc_die ();
-         buffer = (char *) alloca (linklen + 1);
-         size = readlink (p, buffer, linklen + 1);
-         if (size < 0)
+         if (fd < 0)
            {
            {
-             readlink_diag (p);
-             return;
+             errno = - fd;
+             ok = false;
            }
            }
-         buffer[size] = '\0';
-         assign_string (&st->link_name, buffer);
-         if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size)
-           write_long_link (st);
-
-         block_ordinal = current_block_ordinal ();
-         st->stat.st_size = 0; /* force 0 size on symlink */
-         header = start_header (st);
-         if (!header)
-           return;
-         tar_copy_str (header->header.linkname, buffer, NAME_FIELD_SIZE);
-         header->header.typeflag = SYMTYPE;
-         finish_header (st, header, block_ordinal);
-         /* nothing more to do to it */
-
-         if (remove_files_option)
+         else if (fd == 0)
            {
            {
-             if (unlink (p) == -1)
-               unlink_error (p);
+             if (parentfd < 0 && ! top_level)
+               {
+                 errno = - parentfd;
+                 ok = false;
+               }
+             else
+               ok = fstatat (parentfd, name, &final_stat, fstatat_flags) == 0;
            }
            }
-         file_count_links (st);
-         return;
-       }
-#endif
-      else if (S_ISCHR (st->stat.st_mode))
-       type = CHRTYPE;
-      else if (S_ISBLK (st->stat.st_mode))
-       type = BLKTYPE;
-      else if (S_ISFIFO (st->stat.st_mode))
-       type = FIFOTYPE;
-      else if (S_ISSOCK (st->stat.st_mode))
-       {
-         WARN ((0, 0, _("%s: socket ignored"), quotearg_colon (p)));
-         return;
+         else
+           ok = fstat (fd, &final_stat) == 0;
+
+         if (! ok)
+           file_removed_diag (p, top_level, stat_diag);
        }
        }
-      else if (S_ISDOOR (st->stat.st_mode))
+
+      if (ok)
        {
        {
-         WARN ((0, 0, _("%s: door ignored"), quotearg_colon (p)));
-         return;
+         if ((timespec_cmp (get_stat_ctime (&final_stat), original_ctime) != 0
+              /* Original ctime will change if the file is a directory and
+                 --remove-files is given */
+              && !(remove_files_option && is_dir))
+             || original_size < final_stat.st_size)
+           {
+             WARNOPT (WARN_FILE_CHANGED,
+                      (0, 0, _("%s: file changed as we read it"),
+                       quotearg_colon (p)));
+             set_exit_status (TAREXIT_DIFFERS);
+           }
+         else if (atime_preserve_option == replace_atime_preserve
+                  && set_file_atime (fd, p, restore_times) != 0)
+           utime_error (p);
        }
        }
-      else
+
+      ok &= tar_stat_close (st);
+      if (ok && remove_files_option)
+       queue_deferred_unlink (p, is_dir);
+
+      return;
+    }
+#ifdef HAVE_READLINK
+  else if (S_ISLNK (st->stat.st_mode))
+    {
+      char *buffer;
+      int size;
+      size_t linklen = st->stat.st_size;
+      if (linklen != st->stat.st_size || linklen + 1 == 0)
+       xalloc_die ();
+      buffer = (char *) alloca (linklen + 1);
+      size = readlinkat (parentfd, name, buffer, linklen + 1);
+      if (size < 0)
        {
        {
-         unknown_file_error (p);
+         file_removed_diag (p, top_level, readlink_diag);
          return;
        }
          return;
        }
+      buffer[size] = '\0';
+      assign_string (&st->link_name, buffer);
+      transform_name (&st->link_name, XFORM_SYMLINK);
+      if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size)
+       write_long_link (st);
+
+      block_ordinal = current_block_ordinal ();
+      st->stat.st_size = 0;    /* force 0 size on symlink */
+      header = start_header (st);
+      if (!header)
+       return;
+      tar_copy_str (header->header.linkname, st->link_name, NAME_FIELD_SIZE);
+      header->header.typeflag = SYMTYPE;
+      finish_header (st, header, block_ordinal);
+      /* nothing more to do to it */
+
+      if (remove_files_option)
+       queue_deferred_unlink (p, false);
+
+      file_count_links (st);
+      return;
+    }
+#endif
+  else if (S_ISCHR (st->stat.st_mode))
+    type = CHRTYPE;
+  else if (S_ISBLK (st->stat.st_mode))
+    type = BLKTYPE;
+  else if (S_ISFIFO (st->stat.st_mode))
+    type = FIFOTYPE;
+  else if (S_ISSOCK (st->stat.st_mode))
+    {
+      WARNOPT (WARN_FILE_IGNORED,
+              (0, 0, _("%s: socket ignored"), quotearg_colon (p)));
+      return;
+    }
+  else if (S_ISDOOR (st->stat.st_mode))
+    {
+      WARNOPT (WARN_FILE_IGNORED,
+              (0, 0, _("%s: door ignored"), quotearg_colon (p)));
+      return;
+    }
+  else
+    {
+      unknown_file_error (p);
+      return;
     }
 
   if (archive_format == V7_FORMAT)
     }
 
   if (archive_format == V7_FORMAT)
@@ -1611,17 +1889,23 @@ dump_file0 (struct tar_stat_info *st, char *p,
 
   finish_header (st, header, block_ordinal);
   if (remove_files_option)
 
   finish_header (st, header, block_ordinal);
   if (remove_files_option)
-    {
-      if (unlink (p) == -1)
-       unlink_error (p);
-    }
+    queue_deferred_unlink (p, false);
 }
 
 }
 
+/* Dump a file, recursively.  PARENT describes the file's parent
+   directory, NAME is the file's name relative to PARENT, and FULLNAME
+   its full name, possibly relative to the working directory.  NAME
+   may contain slashes at the top level of invocation.  */
+
 void
 void
-dump_file (char *p, int top_level, dev_t parent_device)
+dump_file (struct tar_stat_info *parent, char const *name,
+          char const *fullname)
 {
   struct tar_stat_info st;
   tar_stat_init (&st);
 {
   struct tar_stat_info st;
   tar_stat_init (&st);
-  dump_file0 (&st, p, top_level, parent_device);
+  st.parent = parent;
+  dump_file0 (&st, name, fullname);
+  if (parent && listed_incremental_option)
+    update_parent_directory (parent);
   tar_stat_destroy (&st);
 }
   tar_stat_destroy (&st);
 }
This page took 0.063462 seconds and 4 git commands to generate.