re-mark 1.29b-2 as not yet uploaded (merge madness!)
[debian/tar] / src / unlink.c
1 /* Unlink files.
2
3    Copyright 2009, 2013-2014, 2016 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 #define IS_CWD(p) \
36   ((p)->is_dir \
37    && ((p)->file_name[0] == 0 || strcmp ((p)->file_name, ".") == 0))
38
39 /* The unlink queue */
40 static struct deferred_unlink *dunlink_head, *dunlink_tail;
41
42 /* Number of entries in the queue */
43 static size_t dunlink_count;
44
45 /* List of entries available for allocation */
46 static struct deferred_unlink *dunlink_avail;
47
48 /* Delay (number of records written) between adding entry to the
49    list and its actual removal. */
50 static size_t deferred_unlink_delay = 0;
51
52 static struct deferred_unlink *
53 dunlink_alloc (void)
54 {
55   struct deferred_unlink *p;
56   if (dunlink_avail)
57     {
58       p = dunlink_avail;
59       dunlink_avail = p->next;
60       p->next  = NULL;
61     }
62   else
63     p = xmalloc (sizeof (*p));
64   return p;
65 }
66
67 static void
68 dunlink_insert (struct deferred_unlink *anchor, struct deferred_unlink *p)
69 {
70   if (anchor)
71     {
72       p->next = anchor->next;
73       anchor->next = p;
74     }
75   else 
76     {
77       p->next = dunlink_head;
78       dunlink_head = p;
79     }
80   if (!p->next)
81     dunlink_tail = p;
82   dunlink_count++;
83 }
84
85 static void
86 dunlink_reclaim (struct deferred_unlink *p)
87 {
88   free (p->file_name);
89   p->next = dunlink_avail;
90   dunlink_avail = p;
91 }
92
93 static void
94 flush_deferred_unlinks (bool force)
95 {
96   struct deferred_unlink *p, *prev = NULL;
97   int saved_chdir = chdir_current;
98   
99   for (p = dunlink_head; p; )
100     {
101       struct deferred_unlink *next = p->next;
102
103       if (force
104           || records_written > p->records_written + deferred_unlink_delay)
105         {
106           chdir_do (p->dir_idx);
107           if (p->is_dir)
108             {
109               const char *fname;
110
111               if (p->dir_idx && IS_CWD (p))
112                 {
113                   prev = p;
114                   p = next;
115                   continue;
116                 }
117               else
118                 fname = p->file_name;
119
120               if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
121                 {
122                   switch (errno)
123                     {
124                     case ENOENT:
125                       /* nothing to worry about */
126                       break;
127                     case EEXIST:
128                       /* OpenSolaris >=10 sets EEXIST instead of ENOTEMPTY
129                          if trying to remove a non-empty directory */
130                     case ENOTEMPTY:
131                       /* Keep the record in list, in the hope we'll
132                          be able to remove it later */
133                       prev = p;
134                       p = next;
135                       continue;
136
137                     default:
138                       rmdir_error (fname);
139                     }
140                 }
141             }
142           else
143             {
144               if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT)
145                 unlink_error (p->file_name);
146             }
147           dunlink_reclaim (p);
148           dunlink_count--;
149           p = next;
150           if (prev)
151             prev->next = p;
152           else
153             dunlink_head = p;
154         }
155       else
156         {
157           prev = p;
158           p = next;
159         }
160     }
161   if (!dunlink_head)
162     dunlink_tail = NULL;
163   else if (force)
164     {
165       for (p = dunlink_head; p; )
166         {
167           struct deferred_unlink *next = p->next;
168           const char *fname;
169
170           chdir_do (p->dir_idx);
171           if (p->dir_idx && IS_CWD (p))
172             {
173               fname = tar_dirname ();
174               chdir_do (p->dir_idx - 1);
175             }
176           else
177             fname = p->file_name;
178
179           if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
180             {
181               if (errno != ENOENT)
182                 rmdir_error (fname);
183             }
184           dunlink_reclaim (p);
185           dunlink_count--;
186           p = next;
187         }
188       dunlink_head = dunlink_tail = NULL;
189     }     
190             
191   chdir_do (saved_chdir);
192 }
193
194 void
195 finish_deferred_unlinks (void)
196 {
197   flush_deferred_unlinks (true);
198   
199   while (dunlink_avail)
200     {
201       struct deferred_unlink *next = dunlink_avail->next;
202       free (dunlink_avail);
203       dunlink_avail = next;
204     }
205 }
206
207 void
208 queue_deferred_unlink (const char *name, bool is_dir)
209 {
210   struct deferred_unlink *p;
211
212   if (dunlink_head
213       && records_written > dunlink_head->records_written + deferred_unlink_delay)
214     flush_deferred_unlinks (false);
215
216   p = dunlink_alloc ();
217   p->next = NULL;
218   p->dir_idx = chdir_current;
219   p->file_name = xstrdup (name);
220   normalize_filename_x (p->file_name);
221   p->is_dir = is_dir;
222   p->records_written = records_written;
223
224   if (IS_CWD (p))
225     {
226       struct deferred_unlink *q, *prev;
227       for (q = dunlink_head, prev = NULL; q; prev = q, q = q->next)
228         if (IS_CWD (q) && q->dir_idx < p->dir_idx)
229           break;
230       if (q)
231         dunlink_insert (prev, p);
232       else
233         dunlink_insert (dunlink_tail, p);
234     }
235   else
236     dunlink_insert (dunlink_tail, p);
237 }