]> Dogcows Code - chaz/tar/blobdiff - src/create.c
tar: more reliable directory traversal when creating archives
[chaz/tar] / src / create.c
index 79c80ce08282cc65d9bde8ef25b0811e7bac64d7..e137325484e83efd8dd819722e64bda87e1e04fc 100644 (file)
@@ -1,7 +1,7 @@
 /* Create a tar archive.
 
    Copyright (C) 1985, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
-   2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
+   2003, 2004, 2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
 
    Written by John Gilmore, on 1985-08-25.
 
@@ -30,7 +30,7 @@ struct link
   {
     dev_t dev;
     ino_t ino;
-    size_t nlink;
+    nlink_t nlink;
     char name[1];
   };
 
@@ -39,7 +39,7 @@ struct exclusion_tag
   const char *name;
   size_t length;
   enum exclusion_tag_type type;
-  bool (*predicate) (const char *name);
+  bool (*predicate) (int fd);
   struct exclusion_tag *next;
 };
 
@@ -47,7 +47,7 @@ static struct exclusion_tag *exclusion_tags;
 
 void
 add_exclusion_tag (const char *name, enum exclusion_tag_type type,
-                  bool (*predicate) (const char *name))
+                  bool (*predicate) (int fd))
 {
   struct exclusion_tag *tag = xmalloc (sizeof tag[0]);
   tag->next = exclusion_tags;
@@ -71,39 +71,24 @@ exclusion_tag_warning (const char *dirname, const char *tagname,
              message));
 }
 
-enum exclusion_tag_type 
-check_exclusion_tags (const char *dirname, const char **tag_file_name)
+enum exclusion_tag_type
+check_exclusion_tags (int fd, char const **tag_file_name)
 {
-  static char *tagname;
-  static size_t tagsize;
   struct exclusion_tag *tag;
-  size_t dlen = strlen (dirname);
-  int addslash = !ISSLASH (dirname[dlen-1]);
-  char *nptr = NULL;
-  
+
   for (tag = exclusion_tags; tag; tag = tag->next)
     {
-      size_t size = dlen + addslash + tag->length + 1;
-      if (size > tagsize)
-       {
-         tagsize = size;
-         tagname = xrealloc (tagname, tagsize);
-       }
-
-      if (!nptr)
+      int tagfd = openat (fd, tag->name, open_read_flags);
+      if (0 <= tagfd)
        {
-         strcpy (tagname, dirname);
-         nptr = tagname + dlen;
-         if (addslash)
-           *nptr++ = '/';
-       }
-      strcpy (nptr, tag->name);
-      if (access (tagname, F_OK) == 0
-         && (!tag->predicate || tag->predicate (tagname)))
-       {
-         if (tag_file_name)
-           *tag_file_name = tag->name;
-         return tag->type;
+         bool satisfied = !tag->predicate || tag->predicate (tagfd);
+         close (tagfd);
+         if (satisfied)
+           {
+             if (tag_file_name)
+               *tag_file_name = tag->name;
+             return tag->type;
+           }
        }
     }
 
@@ -121,22 +106,13 @@ check_exclusion_tags (const char *dirname, const char **tag_file_name)
 #define CACHEDIR_SIGNATURE_SIZE (sizeof CACHEDIR_SIGNATURE - 1)
 
 bool
-cachedir_file_p (const char *name)
+cachedir_file_p (int fd)
 {
-  bool tag_present = false;
-  int fd = open (name, O_RDONLY);
-  if (fd >= 0)
-    {
-      static char tagbuf[CACHEDIR_SIGNATURE_SIZE];
+  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);
-    }
-  return tag_present;
+  return
+    (read (fd, tagbuf, CACHEDIR_SIGNATURE_SIZE) == CACHEDIR_SIGNATURE_SIZE
+     && memcmp (tagbuf, CACHEDIR_SIGNATURE, CACHEDIR_SIGNATURE_SIZE) == 0);
 }
 
 \f
@@ -214,6 +190,14 @@ to_base256 (int negative, uintmax_t value, char *where, size_t size)
   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,
@@ -263,7 +247,7 @@ to_chars_subst (int negative, int gnu_format, uintmax_t value, size_t valsize,
 
         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];
@@ -368,25 +352,25 @@ gid_substitute (int *negative)
   return r;
 }
 
-bool
+static bool
 gid_to_chars (gid_t v, char *p, size_t s)
 {
   return to_chars (v < 0, (uintmax_t) v, sizeof v, gid_substitute, p, s, "gid_t");
 }
 
-bool
+static bool
 major_to_chars (major_t v, char *p, size_t s)
 {
   return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "major_t");
 }
 
-bool
+static bool
 minor_to_chars (minor_t v, char *p, size_t s)
 {
   return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "minor_t");
 }
 
-bool
+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,
@@ -432,12 +416,6 @@ off_to_chars (off_t v, char *p, size_t s)
   return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "off_t");
 }
 
-bool
-size_to_chars (size_t v, char *p, size_t s)
-{
-  return to_chars (0, (uintmax_t) v, sizeof v, 0, p, s, "size_t");
-}
-
 bool
 time_to_chars (time_t v, char *p, size_t s)
 {
@@ -460,19 +438,19 @@ uid_substitute (int *negative)
   return r;
 }
 
-bool
+static bool
 uid_to_chars (uid_t v, char *p, size_t s)
 {
   return to_chars (v < 0, (uintmax_t) v, sizeof v, uid_substitute, p, s, "uid_t");
 }
 
-bool
+static bool
 uintmax_to_chars (uintmax_t v, char *p, size_t s)
 {
   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);
@@ -480,20 +458,25 @@ string_to_chars (char const *str, char *p, size_t s)
 }
 
 \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:
 
    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)
-    return totals_option && sparse_option && ST_IS_SPARSE (st->stat);
-  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
@@ -515,9 +498,8 @@ write_eot (void)
 
 /* 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));
@@ -525,7 +507,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);
 
-  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);
@@ -563,13 +544,13 @@ write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
   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);
@@ -604,8 +585,10 @@ split_long_name (const char *name, size_t length)
 {
   size_t i;
 
-  if (length > PREFIX_FIELD_SIZE)
+  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;
@@ -616,7 +599,7 @@ static union block *
 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)
@@ -628,7 +611,7 @@ write_ustar_long_name (const char *name)
     }
 
   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"),
@@ -712,6 +695,7 @@ write_extended (bool global, struct tar_stat_info *st, union block *old_header)
   union block *header, hp;
   char *p;
   int type;
+  time_t t;
 
   if (st->xhdr.buffer || st->xhdr.stk == NULL)
     return old_header;
@@ -722,13 +706,15 @@ write_extended (bool global, struct tar_stat_info *st, union block *old_header)
     {
       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, &st->xhdr);
+  xheader_write (type, p, t, &st->xhdr);
   free (p);
   header = find_next_block ();
   memcpy (header, &hp.buffer, sizeof (hp.buffer));
@@ -993,11 +979,9 @@ finish_header (struct tar_stat_info *st,
       && 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;
-      print_header (st, block_ordinal);
+      print_header (st, header, block_ordinal);
     }
 
   header = write_extended (false, st, header);
@@ -1011,7 +995,6 @@ pad_archive (off_t size_left)
   union block *blk;
   while (size_left > 0)
     {
-      mv_size_left (size_left);
       blk = find_next_block ();
       memset (blk->buffer, 0, BLOCKSIZE);
       set_next_block_after (blk);
@@ -1037,12 +1020,10 @@ dump_regular_file (int fd, struct tar_stat_info *st)
 
   finish_header (st, blk, block_ordinal);
 
-  mv_begin (st);
+  mv_begin_write (st->file_name, st->stat.st_size, st->stat.st_size);
   while (size_left > 0)
     {
       size_t bufsize, count;
-      
-      mv_size_left (size_left);
 
       blk = find_next_block ();
 
@@ -1079,7 +1060,7 @@ dump_regular_file (int fd, struct tar_stat_info *st)
                              size_left),
                    quotearg_colon (st->orig_file_name),
                    STRINGIFY_BIGINT (size_left, buf)));
-         if (! ignore_failed_read_option) 
+         if (! ignore_failed_read_option)
            set_exit_status (TAREXIT_DIFFERS);
          pad_archive (size_left - (bufsize - count));
          return dump_status_short;
@@ -1089,79 +1070,75 @@ dump_regular_file (int fd, struct tar_stat_info *st)
 }
 
 \f
+/* Copy info from the directory identified by ST into the archive.
+   DIRECTORY contains the directory's entries.  */
+
 static void
-dump_dir0 (char *directory,
-          struct tar_stat_info *st, bool 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;
-  
-  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 */
+  union block *blk = NULL;
+  off_t block_ordinal = current_block_ordinal ();
 
-      blk = start_header (st);
-      if (!blk)
-       return;
+  st->stat.st_size = 0;        /* force 0 size on dir */
 
-      if (incremental_option && archive_format != POSIX_FORMAT)
-       blk->header.typeflag = GNUTYPE_DUMPDIR;
-      else /* if (standard_option) */
-       blk->header.typeflag = DIRTYPE;
+  blk = start_header (st);
+  if (!blk)
+    return;
 
-      /* If we're gnudumping, we aren't done yet so don't close it.  */
+  if (incremental_option && archive_format != POSIX_FORMAT)
+    blk->header.typeflag = GNUTYPE_DUMPDIR;
+  else /* if (standard_option) */
+    blk->header.typeflag = DIRTYPE;
 
-      if (!incremental_option)
-       finish_header (st, blk, block_ordinal);
-      else if (gnu_list_name->directory)
+  /* 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->directory)
+    {
+      if (archive_format == POSIX_FORMAT)
        {
-         if (archive_format == POSIX_FORMAT)
-           {
-             xheader_store ("GNU.dumpdir", st,
-                            safe_directory_contents (gnu_list_name->directory));
-             finish_header (st, blk, block_ordinal);
-           }
-         else
+         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;
+         size_t bufsize;
+         ssize_t count;
+         const char *buffer, *p_buffer;
+
+         block_ordinal = current_block_ordinal ();
+         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;
+
+         mv_begin_write (st->file_name, totsize, totsize);
+         while (size_left > 0)
            {
-             off_t size_left;
-             off_t totsize;
-             size_t bufsize;
-             ssize_t count;
-             const char *buffer, *p_buffer;
-
-             block_ordinal = current_block_ordinal ();
-             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;
-
-             mv_begin (st);
-             mv_total_size (totsize);
-             while (size_left > 0)
+             blk = find_next_block ();
+             bufsize = available_space_after (blk);
+             if (size_left < bufsize)
                {
-                 mv_size_left (size_left);
-                 blk = find_next_block ();
-                 bufsize = available_space_after (blk);
-                 if (size_left < bufsize)
-                   {
-                     bufsize = size_left;
-                     count = bufsize % BLOCKSIZE;
-                     if (count)
-                       memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
-                   }
-                 memcpy (blk->buffer, p_buffer, bufsize);
-                 size_left -= bufsize;
-                 p_buffer += bufsize;
-                 set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
+                 bufsize = size_left;
+                 count = bufsize % BLOCKSIZE;
+                 if (count)
+                   memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
                }
-             mv_end ();
+             memcpy (blk->buffer, p_buffer, bufsize);
+             size_left -= bufsize;
+             p_buffer += bufsize;
+             set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
            }
-         return;
        }
+      return;
     }
 
   if (!recursion_option)
@@ -1169,7 +1146,7 @@ dump_dir0 (char *directory,
 
   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)
        WARNOPT (WARN_XDEV,
@@ -1181,13 +1158,13 @@ dump_dir0 (char *directory,
     {
       char *name_buf;
       size_t name_size;
-      
-      switch (check_exclusion_tags (st->orig_file_name, &tag_file_name))
+
+      switch (check_exclusion_tags (st->fd, &tag_file_name))
        {
        case exclusion_tag_all:
          /* Handled in dump_file0 */
          break;
-         
+
        case exclusion_tag_none:
          {
            char const *entry;
@@ -1198,7 +1175,6 @@ dump_dir0 (char *directory,
            name_size = name_len = strlen (name_buf);
 
            /* Now output all the files in the directory.  */
-           /* FIXME: Should speed this up by cd-ing into the dir.  */
            for (entry = directory; (entry_len = strlen (entry)) != 0;
                 entry += entry_len + 1)
              {
@@ -1209,9 +1185,9 @@ dump_dir0 (char *directory,
                  }
                strcpy (name_buf + name_len, entry);
                if (!excluded_name (name_buf))
-                 dump_file (name_buf, false, our_device);
+                 dump_file (st, entry, name_buf);
              }
-           
+
            free (name_buf);
          }
          break;
@@ -1223,10 +1199,10 @@ dump_dir0 (char *directory,
          name_buf = xmalloc (name_size);
          strcpy (name_buf, st->orig_file_name);
          strcat (name_buf, tag_file_name);
-         dump_file (name_buf, false, our_device);
+         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"));
@@ -1249,22 +1225,38 @@ ensure_slash (char **pstr)
 }
 
 static bool
-dump_dir (int fd, struct tar_stat_info *st, bool top_level,
-         dev_t parent_device)
+dump_dir (struct tar_stat_info *st)
 {
-  char *directory = fdsavedir (fd);
-  if (!directory)
+  char *directory = 0;
+  int dupfd = dup (st->fd);
+  if (0 <= dupfd)
+    {
+      directory = fdsavedir (dupfd);
+      if (! directory)
+       {
+         int e = errno;
+         close (dupfd);
+         errno = e;
+       }
+    }
+  if (! directory)
     {
       savedir_diag (st->orig_file_name);
       return false;
     }
 
-  dump_dir0 (directory, st, top_level, parent_device);
+  dump_dir0 (st, directory);
 
   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.  */
 
@@ -1273,6 +1265,8 @@ create_archive (void)
 {
   struct name const *p;
 
+  trivial_link_count = name_count <= 1 && ! dereference_option;
+
   open_archive (ACCESS_WRITE);
   buffer_write_global_xheader ();
 
@@ -1286,12 +1280,13 @@ create_archive (void)
 
       while ((p = name_from_list ()) != NULL)
        if (!excluded_name (p->name))
-         dump_file (p->name, p->cmdline, (dev_t) 0);
+         dump_file (0, p->name, p->name);
 
       blank_name_list ();
       while ((p = name_from_list ()) != NULL)
        if (!excluded_name (p->name))
          {
+           struct tar_stat_info st;
            size_t plen = strlen (p->name);
            if (buffer_size <= plen)
              {
@@ -1302,6 +1297,7 @@ create_archive (void)
            memcpy (buffer, p->name, plen);
            if (! ISSLASH (buffer[plen - 1]))
              buffer[plen++] = DIRECTORY_SEPARATOR;
+           tar_stat_init (&st);
            q = directory_contents (gnu_list_name->directory);
            if (q)
              while (*q)
@@ -1309,6 +1305,24 @@ create_archive (void)
                  size_t qlen = strlen (q);
                  if (*q == 'Y')
                    {
+                     if (! st.orig_file_name)
+                       {
+                         st.orig_file_name = xstrdup (p->name);
+                         st.fd = open (st.orig_file_name,
+                                       ((open_read_flags - O_RDONLY
+                                         + O_SEARCH)
+                                        | O_DIRECTORY));
+                         if (st.fd < 0)
+                           {
+                             open_diag (p->name);
+                             break;
+                           }
+                         if (fstat (st.fd, &st.stat) != 0)
+                           {
+                             stat_diag (p->name);
+                             break;
+                           }
+                       }
                      if (buffer_size < plen + qlen)
                        {
                          while ((buffer_size *=2 ) < plen + qlen)
@@ -1316,10 +1330,11 @@ create_archive (void)
                          buffer = xrealloc (buffer, buffer_size);
                        }
                      strcpy (buffer + plen, q + 1);
-                     dump_file (buffer, false, (dev_t) 0);
+                     dump_file (&st, q + 1, buffer);
                    }
                  q += qlen + 1;
                }
+           tar_stat_destroy (&st);
          }
       free (buffer);
     }
@@ -1328,12 +1343,12 @@ create_archive (void)
       const char *name;
       while ((name = name_next (1)) != NULL)
        if (!excluded_name (name))
-         dump_file (name, true, (dev_t) 0);
+         dump_file (0, name, name);
     }
 
   write_eot ();
   close_archive ();
-
+  finish_deferred_unlinks ();
   if (listed_incremental_option)
     write_directory_file ();
 }
@@ -1380,7 +1395,8 @@ static Hash_table *link_table;
 static bool
 dump_hard_link (struct tar_stat_info *st)
 {
-  if (link_table && (st->stat.st_nlink > 1 || remove_files_option))
+  if (link_table
+      && (trivial_link_count < st->stat.st_nlink || remove_files_option))
     {
       struct link lp;
       struct link *duplicate;
@@ -1413,8 +1429,8 @@ dump_hard_link (struct tar_stat_info *st)
          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;
        }
@@ -1427,7 +1443,7 @@ file_count_links (struct tar_stat_info *st)
 {
   if (hard_dereference_option)
     return;
-  if (st->stat.st_nlink > 1)
+  if (trivial_link_count < st->stat.st_nlink)
     {
       struct link *duplicate;
       char *linkname = NULL;
@@ -1435,7 +1451,7 @@ file_count_links (struct tar_stat_info *st)
 
       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;
@@ -1443,13 +1459,13 @@ file_count_links (struct tar_stat_info *st)
       lp->nlink = st->stat.st_nlink;
       strcpy (lp->name, linkname);
       free (linkname);
-      
+
       if (! ((link_table
              || (link_table = hash_initialize (0, 0, hash_link,
                                                compare_links, 0)))
             && (duplicate = hash_insert (link_table, lp))))
        xalloc_die ();
-      
+
       if (duplicate != lp)
        abort ();
       lp->nlink--;
@@ -1476,18 +1492,15 @@ check_links (void)
     }
 }
 
-/* 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
-dump_file0 (struct tar_stat_info *st, const char *p,
-           bool top_level, dev_t parent_device)
+dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
 {
   union block *header;
   char type;
@@ -1495,7 +1508,11 @@ dump_file0 (struct tar_stat_info *st, const char *p,
   struct timespec original_ctime;
   struct timespec restore_times[2];
   off_t block_ordinal = -1;
+  int fd = -1;
   bool is_dir;
+  bool top_level = ! st->parent;
+  int parentfd = top_level ? AT_FDCWD : st->parent->fd;
+  void (*diag) (char const *) = 0;
 
   if (interactive_option && !confirm ("add", p))
     return;
@@ -1506,11 +1523,22 @@ dump_file0 (struct tar_stat_info *st, const char *p,
 
   transform_name (&st->file_name, XFORM_REGFILE);
 
-  if (deref_stat (dereference_option, p, &st->stat) != 0)
+  if (fstatat (parentfd, name, &st->stat, fstatat_flags) != 0)
+    diag = stat_diag;
+  else if (file_dumpable_p (&st->stat))
+    {
+      fd = st->fd = openat (parentfd, name, open_read_flags);
+      if (fd < 0)
+       diag = open_diag;
+      else if (fstat (fd, &st->stat) != 0)
+       diag = stat_diag;
+    }
+  if (diag)
     {
-      file_removed_diag (p, top_level, stat_diag);
+      file_removed_diag (p, top_level, diag);
       return;
     }
+
   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);
@@ -1531,10 +1559,10 @@ dump_file0 (struct tar_stat_info *st, const char *p,
 
   /* See if we want only new files, and check if this one is too old to
      put in the archive.
-     
+
      This check is omitted if incremental_option is set *and* the
      requested file is not explicitely listed in the command line. */
-  
+
   if (!(incremental_option && !is_individual_file (p))
       && !S_ISDIR (st->stat.st_mode)
       && OLDER_TAR_STAT_TIME (*st, m)
@@ -1556,9 +1584,6 @@ dump_file0 (struct tar_stat_info *st, const char *p,
       return;
     }
 
-  if (is_avoided_name (p))
-    return;
-
   is_dir = S_ISDIR (st->stat.st_mode) != 0;
 
   if (!is_dir && dump_hard_link (st))
@@ -1567,43 +1592,22 @@ dump_file0 (struct tar_stat_info *st, const char *p,
   if (is_dir || S_ISREG (st->stat.st_mode) || S_ISCTG (st->stat.st_mode))
     {
       bool ok;
-      int fd = -1;
       struct stat final_stat;
 
-      if (is_dir || file_dumpable_p (st))
-       {
-         fd = open (p,
-                    (O_RDONLY | O_BINARY
-                     | (is_dir ? O_DIRECTORY | O_NONBLOCK : 0)
-                     | (atime_preserve_option == system_atime_preserve
-                        ? O_NOATIME
-                        : 0)));
-         if (fd < 0)
-           {
-             file_removed_diag (p, top_level, open_diag);
-             return;
-           }
-       }
-
       if (is_dir)
        {
          const char *tag_file_name;
          ensure_slash (&st->orig_file_name);
          ensure_slash (&st->file_name);
 
-         if (check_exclusion_tags (st->orig_file_name, &tag_file_name)
-             == exclusion_tag_all)
+         if (check_exclusion_tags (fd, &tag_file_name) == exclusion_tag_all)
            {
              exclusion_tag_warning (st->orig_file_name, tag_file_name,
                                     _("directory not dumped"));
              return;
            }
-         
-         ok = dump_dir (fd, st, top_level, parent_device);
 
-         /* dump_dir consumes FD if successful.  */
-         if (ok)
-           fd = -1;
+         ok = dump_dir (st);
        }
       else
        {
@@ -1622,7 +1626,6 @@ dump_file0 (struct tar_stat_info *st, const char *p,
            {
            case dump_status_ok:
            case dump_status_short:
-             mv_end ();
              file_count_links (st);
              break;
 
@@ -1638,15 +1641,8 @@ dump_file0 (struct tar_stat_info *st, const char *p,
 
       if (ok)
        {
-         /* If possible, reopen a directory if we are preserving
-            atimes, so that we can set just the atime on systems with
-            _FIOSATIME.  */
-         if (fd < 0 && is_dir
-             && atime_preserve_option == replace_atime_preserve)
-           fd = open (p, O_RDONLY | O_BINARY | O_DIRECTORY | O_NONBLOCK);
-
          if ((fd < 0
-              ? deref_stat (dereference_option, p, &final_stat)
+              ? fstatat (parentfd, name, &final_stat, fstatat_flags)
               : fstat (fd, &final_stat))
              != 0)
            {
@@ -1673,26 +1669,19 @@ dump_file0 (struct tar_stat_info *st, const char *p,
            utime_error (p);
        }
 
-      if (0 <= fd && close (fd) != 0)
-       {
-         close_diag (p);
-         ok = false;
-       }
-
-      if (ok && remove_files_option)
+      if (0 < fd)
        {
-         if (is_dir)
+         if (close (fd) != 0)
            {
-             if (rmdir (p) != 0 && errno != ENOTEMPTY)
-               rmdir_error (p);
-           }
-         else
-           {
-             if (unlink (p) != 0)
-               unlink_error (p);
+             close_diag (p);
+             ok = false;
            }
+         st->fd = 0;
        }
 
+      if (ok && remove_files_option)
+       queue_deferred_unlink (p, is_dir);
+
       return;
     }
 #ifdef HAVE_READLINK
@@ -1704,7 +1693,7 @@ dump_file0 (struct tar_stat_info *st, const char *p,
       if (linklen != st->stat.st_size || linklen + 1 == 0)
        xalloc_die ();
       buffer = (char *) alloca (linklen + 1);
-      size = readlink (p, buffer, linklen + 1);
+      size = readlinkat (parentfd, name, buffer, linklen + 1);
       if (size < 0)
        {
          file_removed_diag (p, top_level, readlink_diag);
@@ -1727,10 +1716,8 @@ dump_file0 (struct tar_stat_info *st, const char *p,
       /* nothing more to do to it */
 
       if (remove_files_option)
-       {
-         if (unlink (p) == -1)
-           unlink_error (p);
-       }
+       queue_deferred_unlink (p, false);
+
       file_count_links (st);
       return;
     }
@@ -1782,19 +1769,23 @@ dump_file0 (struct tar_stat_info *st, const char *p,
 
   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
-dump_file (const char *p, bool 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);
-  dump_file0 (&st, p, top_level, parent_device);
-  if (listed_incremental_option)
-    update_parent_directory (p);
+  st.parent = parent;
+  dump_file0 (&st, name, fullname);
+  if (parent && listed_incremental_option)
+    update_parent_directory (parent);
   tar_stat_destroy (&st);
 }
This page took 0.04345 seconds and 4 git commands to generate.