re-mark 1.29b-2 as not yet uploaded (merge madness!)
[debian/tar] / src / unlink.c
index 2af6f9957b2887eea74b9fe0b394207d60937e32..6ae51ce1fc1e575cb5dc4c57da865cc42c2f5628 100644 (file)
@@ -1,19 +1,21 @@
-/* This file is part of GNU tar. 
-   Copyright (C) 2009 Free Software Foundation, Inc.
+/* Unlink files.
 
-   This program is free software; you can redistribute it and/or modify it
-   under the terms of the GNU General Public License as published by the
-   Free Software Foundation; either version 3, or (at your option) any later
-   version.
+   Copyright 2009, 2013-2014, 2016 Free Software Foundation, Inc.
 
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
-   Public License for more details.
+   This file is part of GNU tar.
 
-   You should have received a copy of the GNU General Public License along
-   with this program; if not, write to the Free Software Foundation, Inc.,
-   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+   GNU tar is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   GNU tar is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <system.h>
 #include "common.h"
 struct deferred_unlink
   {
     struct deferred_unlink *next;   /* Next unlink in the queue */
-    char *file_name;                /* Absolute name of the file to unlink */
+    int dir_idx;                    /* Directory index in wd */
+    char *file_name;                /* Name of the file to unlink, relative
+                                      to dir_idx */
     bool is_dir;                    /* True if file_name is a directory */
     off_t records_written;          /* Number of records written when this
                                       entry got added to the queue */
   };
 
+#define IS_CWD(p) \
+  ((p)->is_dir \
+   && ((p)->file_name[0] == 0 || strcmp ((p)->file_name, ".") == 0))
+
 /* The unlink queue */
 static struct deferred_unlink *dunlink_head, *dunlink_tail;
 
@@ -39,10 +47,10 @@ static struct deferred_unlink *dunlink_avail;
 
 /* Delay (number of records written) between adding entry to the
    list and its actual removal. */
-size_t deferred_unlink_delay = 0;
+static size_t deferred_unlink_delay = 0;
 
 static struct deferred_unlink *
-dunlink_alloc ()
+dunlink_alloc (void)
 {
   struct deferred_unlink *p;
   if (dunlink_avail)
@@ -56,6 +64,24 @@ dunlink_alloc ()
   return p;
 }
 
+static void
+dunlink_insert (struct deferred_unlink *anchor, struct deferred_unlink *p)
+{
+  if (anchor)
+    {
+      p->next = anchor->next;
+      anchor->next = p;
+    }
+  else 
+    {
+      p->next = dunlink_head;
+      dunlink_head = p;
+    }
+  if (!p->next)
+    dunlink_tail = p;
+  dunlink_count++;
+}
+
 static void
 dunlink_reclaim (struct deferred_unlink *p)
 {
@@ -68,40 +94,54 @@ static void
 flush_deferred_unlinks (bool force)
 {
   struct deferred_unlink *p, *prev = NULL;
-
+  int saved_chdir = chdir_current;
+  
   for (p = dunlink_head; p; )
     {
       struct deferred_unlink *next = p->next;
+
       if (force
          || records_written > p->records_written + deferred_unlink_delay)
        {
+         chdir_do (p->dir_idx);
          if (p->is_dir)
            {
-             if (rmdir (p->file_name) != 0)
+             const char *fname;
+
+             if (p->dir_idx && IS_CWD (p))
+               {
+                 prev = p;
+                 p = next;
+                 continue;
+               }
+             else
+               fname = p->file_name;
+
+             if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
                {
                  switch (errno)
                    {
                    case ENOENT:
                      /* nothing to worry about */
                      break;
+                   case EEXIST:
+                     /* OpenSolaris >=10 sets EEXIST instead of ENOTEMPTY
+                        if trying to remove a non-empty directory */
                    case ENOTEMPTY:
-                     if (!force)
-                       {
-                         /* Keep the record in list, in the hope we'll
-                            be able to remove it later */
-                         prev = p;
-                         p = next;
-                         continue;
-                       }
-                     /* fall through */
+                     /* Keep the record in list, in the hope we'll
+                        be able to remove it later */
+                     prev = p;
+                     p = next;
+                     continue;
+
                    default:
-                     rmdir_error (p->file_name);
+                     rmdir_error (fname);
                    }
                }
            }
          else
            {
-             if (unlink (p->file_name) != 0 && errno != ENOENT)
+             if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT)
                unlink_error (p->file_name);
            }
          dunlink_reclaim (p);
@@ -116,16 +156,46 @@ flush_deferred_unlinks (bool force)
        {
          prev = p;
          p = next;
-       }         
+       }
     }
   if (!dunlink_head)
     dunlink_tail = NULL;
+  else if (force)
+    {
+      for (p = dunlink_head; p; )
+       {
+         struct deferred_unlink *next = p->next;
+         const char *fname;
+
+         chdir_do (p->dir_idx);
+         if (p->dir_idx && IS_CWD (p))
+           {
+             fname = tar_dirname ();
+             chdir_do (p->dir_idx - 1);
+           }
+         else
+           fname = p->file_name;
+
+         if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
+           {
+             if (errno != ENOENT)
+               rmdir_error (fname);
+           }
+         dunlink_reclaim (p);
+         dunlink_count--;
+         p = next;
+       }
+      dunlink_head = dunlink_tail = NULL;
+    }    
+           
+  chdir_do (saved_chdir);
 }
 
 void
-finish_deferred_unlinks ()
+finish_deferred_unlinks (void)
 {
   flush_deferred_unlinks (true);
+  
   while (dunlink_avail)
     {
       struct deferred_unlink *next = dunlink_avail->next;
@@ -142,17 +212,26 @@ queue_deferred_unlink (const char *name, bool is_dir)
   if (dunlink_head
       && records_written > dunlink_head->records_written + deferred_unlink_delay)
     flush_deferred_unlinks (false);
-  
+
   p = dunlink_alloc ();
   p->next = NULL;
-  p->file_name = normalize_filename (name);
+  p->dir_idx = chdir_current;
+  p->file_name = xstrdup (name);
+  normalize_filename_x (p->file_name);
   p->is_dir = is_dir;
   p->records_written = records_written;
-  
-  if (dunlink_tail)
-    dunlink_tail->next = p;
+
+  if (IS_CWD (p))
+    {
+      struct deferred_unlink *q, *prev;
+      for (q = dunlink_head, prev = NULL; q; prev = q, q = q->next)
+       if (IS_CWD (q) && q->dir_idx < p->dir_idx)
+         break;
+      if (q)
+       dunlink_insert (prev, p);
+      else
+       dunlink_insert (dunlink_tail, p);
+    }
   else
-    dunlink_head = p;
-  dunlink_tail = p;
-  dunlink_count++;
+    dunlink_insert (dunlink_tail, p);
 }