1 /* Unlink files.
2
3 Copyright 2009-2023 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 #if defined ENOTEMPTY && ENOTEMPTY != EEXIST
131 case ENOTEMPTY:
132 #endif
133 /* Keep the record in list, in the hope we'll
134 be able to remove it later */
135 prev = p;
136 p = next;
137 continue;
138
139 default:
140 rmdir_error (fname);
141 }
142 }
143 }
144 else
145 {
146 if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT)
147 unlink_error (p->file_name);
148 }
149 dunlink_reclaim (p);
150 dunlink_count--;
151 p = next;
152 if (prev)
153 prev->next = p;
154 else
155 dunlink_head = p;
156 }
157 else
158 {
159 prev = p;
160 p = next;
161 }
162 }
163 if (!dunlink_head)
164 dunlink_tail = NULL;
165 else if (force)
166 {
167 for (p = dunlink_head; p; )
168 {
169 struct deferred_unlink *next = p->next;
170 const char *fname;
171
172 chdir_do (p->dir_idx);
173 if (p->dir_idx && IS_CWD (p))
174 {
175 fname = tar_dirname ();
176 chdir_do (p->dir_idx - 1);
177 }
178 else
179 fname = p->file_name;
180
181 if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
182 {
183 if (errno != ENOENT)
184 rmdir_error (fname);
185 }
186 dunlink_reclaim (p);
187 dunlink_count--;
188 p = next;
189 }
190 dunlink_head = dunlink_tail = NULL;
191 }
192
193 chdir_do (saved_chdir);
194 }
195
196 void
197 finish_deferred_unlinks (void)
198 {
199 flush_deferred_unlinks (true);
200
201 while (dunlink_avail)
202 {
203 struct deferred_unlink *next = dunlink_avail->next;
204 free (dunlink_avail);
205 dunlink_avail = next;
206 }
207 }
208
209 void
210 queue_deferred_unlink (const char *name, bool is_dir)
211 {
212 struct deferred_unlink *p;
213
214 if (dunlink_head
215 && records_written > dunlink_head->records_written + deferred_unlink_delay)
216 flush_deferred_unlinks (false);
217
218 p = dunlink_alloc ();
219 p->next = NULL;
220 p->dir_idx = chdir_current;
221 p->file_name = xstrdup (name);
222 normalize_filename_x (p->file_name);
223 p->is_dir = is_dir;
224 p->records_written = records_written;
225
226 if (IS_CWD (p))
227 {
228 struct deferred_unlink *q, *prev;
229 for (q = dunlink_head, prev = NULL; q; prev = q, q = q->next)
230 if (IS_CWD (q) && q->dir_idx < p->dir_idx)
231 break;
232 if (q)
233 dunlink_insert (prev, p);
234 else
235 dunlink_insert (dunlink_tail, p);
236 }
237 else
238 dunlink_insert (dunlink_tail, p);
239 }