Import upstream version 1.28
[debian/tar] / src / unlink.c
1 /* Unlink files.
2
3    Copyright 2009, 2013-2014 Free Software Foundation, Inc.
4
5    This file is part of GNU tar.
6
7    GNU tar is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11
12    GNU tar is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20 #include <system.h>
21 #include "common.h"
22 #include <quotearg.h>
23
24 struct deferred_unlink
25   {
26     struct deferred_unlink *next;   /* Next unlink in the queue */
27     int dir_idx;                    /* Directory index in wd */
28     char *file_name;                /* Name of the file to unlink, relative
29                                        to dir_idx */
30     bool is_dir;                    /* True if file_name is a directory */
31     off_t records_written;          /* Number of records written when this
32                                        entry got added to the queue */
33   };
34
35 /* The unlink queue */
36 static struct deferred_unlink *dunlink_head, *dunlink_tail;
37
38 /* Number of entries in the queue */
39 static size_t dunlink_count;
40
41 /* List of entries available for allocation */
42 static struct deferred_unlink *dunlink_avail;
43
44 /* Delay (number of records written) between adding entry to the
45    list and its actual removal. */
46 static size_t deferred_unlink_delay = 0;
47
48 static struct deferred_unlink *
49 dunlink_alloc (void)
50 {
51   struct deferred_unlink *p;
52   if (dunlink_avail)
53     {
54       p = dunlink_avail;
55       dunlink_avail = p->next;
56       p->next  = NULL;
57     }
58   else
59     p = xmalloc (sizeof (*p));
60   return p;
61 }
62
63 static void
64 dunlink_reclaim (struct deferred_unlink *p)
65 {
66   free (p->file_name);
67   p->next = dunlink_avail;
68   dunlink_avail = p;
69 }
70
71 static void
72 flush_deferred_unlinks (bool force)
73 {
74   struct deferred_unlink *p, *prev = NULL;
75   int saved_chdir = chdir_current;
76
77   for (p = dunlink_head; p; )
78     {
79       struct deferred_unlink *next = p->next;
80
81       if (force
82           || records_written > p->records_written + deferred_unlink_delay)
83         {
84           chdir_do (p->dir_idx);
85           if (p->is_dir)
86             {
87               const char *fname;
88
89               if (p->dir_idx
90                   && (p->file_name[0] == 0
91                       || strcmp (p->file_name, ".") == 0))
92                 {
93                   fname = tar_dirname ();
94                   chdir_do (p->dir_idx - 1);
95                 }
96               else
97                 fname = p->file_name;
98
99               if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
100                 {
101                   switch (errno)
102                     {
103                     case ENOENT:
104                       /* nothing to worry about */
105                       break;
106                     case ENOTEMPTY:
107                       if (!force)
108                         {
109                           /* Keep the record in list, in the hope we'll
110                              be able to remove it later */
111                           prev = p;
112                           p = next;
113                           continue;
114                         }
115                       /* fall through */
116                     default:
117                       rmdir_error (fname);
118                     }
119                 }
120             }
121           else
122             {
123               if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT)
124                 unlink_error (p->file_name);
125             }
126           dunlink_reclaim (p);
127           dunlink_count--;
128           p = next;
129           if (prev)
130             prev->next = p;
131           else
132             dunlink_head = p;
133         }
134       else
135         {
136           prev = p;
137           p = next;
138         }
139     }
140   if (!dunlink_head)
141     dunlink_tail = NULL;
142   chdir_do (saved_chdir);
143 }
144
145 void
146 finish_deferred_unlinks (void)
147 {
148   flush_deferred_unlinks (true);
149   while (dunlink_avail)
150     {
151       struct deferred_unlink *next = dunlink_avail->next;
152       free (dunlink_avail);
153       dunlink_avail = next;
154     }
155 }
156
157 void
158 queue_deferred_unlink (const char *name, bool is_dir)
159 {
160   struct deferred_unlink *p;
161
162   if (dunlink_head
163       && records_written > dunlink_head->records_written + deferred_unlink_delay)
164     flush_deferred_unlinks (false);
165
166   p = dunlink_alloc ();
167   p->next = NULL;
168   p->dir_idx = chdir_current;
169   p->file_name = xstrdup (name);
170   normalize_filename_x (p->file_name);
171   p->is_dir = is_dir;
172   p->records_written = records_written;
173
174   if (dunlink_tail)
175     dunlink_tail->next = p;
176   else
177     dunlink_head = p;
178   dunlink_tail = p;
179   dunlink_count++;
180 }