]> git.ipfire.org Git - thirdparty/tar.git/blame_incremental - src/delete.c
Sync bootstrap from Gnulib
[thirdparty/tar.git] / src / delete.c
... / ...
CommitLineData
1/* Delete entries from a tar archive.
2
3 Copyright 1988-2025 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
22#include "common.h"
23#include <rmt.h>
24
25static union block *new_record;
26static idx_t new_blocks;
27static bool acting_as_filter;
28
29/* The number of records skipped at the start of the archive, when
30 passing over members that are not deleted. */
31off_t records_skipped;
32
33/* Move archive descriptor by COUNT records worth. If COUNT is
34 positive we move forward, else we move negative. If it's a tape,
35 MTIOCTOP had better work. If it's something else, we try to seek
36 on it. If we can't seek, we lose! */
37static void
38move_archive (off_t count)
39{
40 if (count == 0)
41 return;
42
43 if (mtioseek (false, count))
44 return;
45
46 off_t position0 = rmtlseek (archive, 0, SEEK_CUR), position = 0;
47 if (0 <= position0)
48 {
49 /* Pretend the starting position is at the first record
50 boundary after POSITION0. This is useful at EOF after
51 a short read. */
52 idx_t short_size = position0 % record_size;
53 idx_t start_offset = short_size ? record_size - short_size : 0;
54 off_t increment, move_start;
55 if (ckd_mul (&increment, record_size, count)
56 || ckd_add (&move_start, position0, start_offset)
57 || ckd_add (&position, move_start, increment)
58 || position < 0)
59 {
60 paxerror (EOVERFLOW, "lseek: %s", archive_name_array[0]);
61 return;
62 }
63 else if (rmtlseek (archive, position, SEEK_SET) == position)
64 return;
65 }
66 if (!_isrmt (archive))
67 seek_error_details (archive_name_array[0], position);
68}
69
70/* Write out the record which has been filled. If MOVE_BACK_FLAG,
71 backspace to where we started. */
72static void
73write_record (bool move_back_flag)
74{
75 union block *save_record = record_start;
76 record_start = new_record;
77
78 if (acting_as_filter)
79 {
80 archive = STDOUT_FILENO;
81 flush_write ();
82 archive = STDIN_FILENO;
83 }
84 else
85 {
86 move_archive ((records_written + records_skipped) - records_read);
87 flush_write ();
88 }
89
90 record_start = save_record;
91
92 if (move_back_flag)
93 {
94 /* Move the tape head back to where we were. */
95
96 if (! acting_as_filter)
97 move_archive (records_read - (records_written + records_skipped));
98 }
99
100 new_blocks = 0;
101}
102
103static void
104write_recent_blocks (union block *h, idx_t blocks)
105{
106 for (idx_t i = 0; i < blocks; i++)
107 {
108 new_record[new_blocks++] = h[i];
109 if (new_blocks == blocking_factor)
110 write_record (true);
111 }
112}
113
114static void
115write_recent_bytes (char *data, idx_t bytes)
116{
117 idx_t blocks = bytes >> LG_BLOCKSIZE;
118 idx_t rest = bytes & (BLOCKSIZE - 1);
119
120 write_recent_blocks ((union block *)data, blocks);
121 memcpy (new_record[new_blocks].buffer, data + blocks * BLOCKSIZE, rest);
122 if (rest < BLOCKSIZE)
123 memset (new_record[new_blocks].buffer + rest, 0, BLOCKSIZE - rest);
124 new_blocks++;
125 if (new_blocks == blocking_factor)
126 write_record (true);
127}
128
129static void
130flush_file (void)
131{
132 set_next_block_after (current_header);
133 off_t size = current_stat_info.stat.st_size;
134 off_t blocks_to_skip = (size >> LG_BLOCKSIZE) + !!(size & (BLOCKSIZE - 1));
135
136 while (record_end - current_block <= blocks_to_skip)
137 {
138 blocks_to_skip -= (record_end - current_block);
139 flush_archive ();
140 if (record_end == current_block)
141 /* Hit EOF */
142 return;
143 }
144 current_block += blocks_to_skip;
145}
146
147void
148delete_archive_members (void)
149{
150 enum read_header logical_status = HEADER_STILL_UNREAD;
151 enum read_header previous_status = HEADER_STILL_UNREAD;
152
153 /* FIXME: Should clean the routine before cleaning these variables :-( */
154 struct name *name;
155 off_t blocks_to_keep = 0;
156 ptrdiff_t kept_blocks_in_record;
157
158 name_gather ();
159 open_archive (ACCESS_UPDATE);
160 acting_as_filter = strcmp (archive_name_array[0], "-") == 0;
161
162 /* Skip to the first member that matches the name list. */
163 do
164 {
165 enum read_header status = read_header (&current_header,
166 &current_stat_info,
167 read_header_x_raw);
168
169 switch (status)
170 {
171 case HEADER_STILL_UNREAD:
172 abort ();
173
174 case HEADER_SUCCESS:
175 if ((name = name_scan (current_stat_info.file_name, false)) == NULL)
176 {
177 skim_member (acting_as_filter);
178 break;
179 }
180 name->found_count++;
181 if (!isfound (name))
182 {
183 skim_member (acting_as_filter);
184 break;
185 }
186 FALLTHROUGH;
187 case HEADER_SUCCESS_EXTENDED:
188 logical_status = status;
189 break;
190
191 case HEADER_ZERO_BLOCK:
192 if (ignore_zeros_option)
193 {
194 set_next_block_after (current_header);
195 break;
196 }
197 FALLTHROUGH;
198 case HEADER_END_OF_FILE:
199 logical_status = HEADER_END_OF_FILE;
200 break;
201
202 case HEADER_FAILURE:
203 set_next_block_after (current_header);
204 switch (previous_status)
205 {
206 case HEADER_STILL_UNREAD:
207 paxwarn (0, _("This does not look like a tar archive"));
208 FALLTHROUGH;
209 case HEADER_SUCCESS:
210 case HEADER_SUCCESS_EXTENDED:
211 case HEADER_ZERO_BLOCK:
212 paxerror (0, _("Skipping to next header"));
213 FALLTHROUGH;
214 case HEADER_FAILURE:
215 break;
216
217 case HEADER_END_OF_FILE:
218 abort ();
219 }
220 break;
221 }
222
223 previous_status = status;
224 }
225 while (logical_status == HEADER_STILL_UNREAD);
226
227 records_skipped = records_read - 1;
228 new_record = xmalloc (record_size);
229
230 if (logical_status == HEADER_SUCCESS
231 || logical_status == HEADER_SUCCESS_EXTENDED)
232 {
233 write_archive_to_stdout = false;
234
235 /* Save away blocks before this one in this record. */
236
237 new_blocks = current_block - record_start;
238 if (new_blocks)
239 memcpy (new_record, record_start, new_blocks * BLOCKSIZE);
240
241 if (logical_status == HEADER_SUCCESS)
242 {
243 logical_status = HEADER_STILL_UNREAD;
244 flush_file ();
245 }
246
247 /* Skip matching members and move the rest up the archive. */
248 while (logical_status != HEADER_END_OF_FILE)
249 {
250 enum read_header status;
251
252 /* Fill in a record. */
253
254 if (current_block == record_end)
255 flush_archive ();
256
257 status = read_header (&current_header, &current_stat_info,
258 read_header_auto);
259
260 switch (status)
261 {
262 case HEADER_STILL_UNREAD:
263 case HEADER_SUCCESS_EXTENDED:
264 abort ();
265
266 case HEADER_SUCCESS:
267 /* Found another header. */
268 xheader_decode (&current_stat_info);
269
270 if ((name = name_scan (current_stat_info.file_name, false)) != NULL)
271 {
272 name->found_count++;
273 if (isfound (name))
274 {
275 flush_file ();
276 break;
277 }
278 }
279 /* Copy header. */
280
281 if (current_stat_info.xhdr.size)
282 {
283 write_recent_bytes (current_stat_info.xhdr.buffer,
284 current_stat_info.xhdr.size);
285 }
286 else
287 {
288 write_recent_blocks (recent_long_name,
289 recent_long_name_blocks);
290 write_recent_blocks (recent_long_link,
291 recent_long_link_blocks);
292 }
293 new_record[new_blocks] = *current_header;
294 new_blocks++;
295 blocks_to_keep
296 = ((current_stat_info.stat.st_size >> LG_BLOCKSIZE)
297 + !!(current_stat_info.stat.st_size & (BLOCKSIZE - 1)));
298 set_next_block_after (current_header);
299 if (new_blocks == blocking_factor)
300 write_record (true);
301
302 /* Copy data. */
303
304 kept_blocks_in_record = record_end - current_block;
305 if (kept_blocks_in_record > blocks_to_keep)
306 kept_blocks_in_record = blocks_to_keep;
307
308 while (blocks_to_keep)
309 {
310 ptrdiff_t count;
311
312 if (current_block == record_end)
313 {
314 flush_read ();
315 current_block = record_start;
316 kept_blocks_in_record = blocking_factor;
317 if (kept_blocks_in_record > blocks_to_keep)
318 kept_blocks_in_record = blocks_to_keep;
319 }
320 count = kept_blocks_in_record;
321 if (blocking_factor - new_blocks < count)
322 count = blocking_factor - new_blocks;
323
324 if (! count)
325 abort ();
326
327 memcpy (new_record + new_blocks, current_block,
328 count * BLOCKSIZE);
329 new_blocks += count;
330 current_block += count;
331 blocks_to_keep -= count;
332 kept_blocks_in_record -= count;
333
334 if (new_blocks == blocking_factor)
335 write_record (true);
336 }
337 break;
338
339 case HEADER_ZERO_BLOCK:
340 if (ignore_zeros_option)
341 set_next_block_after (current_header);
342 else
343 logical_status = HEADER_END_OF_FILE;
344 break;
345
346 case HEADER_END_OF_FILE:
347 logical_status = HEADER_END_OF_FILE;
348 break;
349
350 case HEADER_FAILURE:
351 paxerror (0, _("Deleting non-header from archive"));
352 set_next_block_after (current_header);
353 break;
354
355 default:
356 abort ();
357 }
358 tar_stat_destroy (&current_stat_info);
359 }
360
361 if (logical_status == HEADER_END_OF_FILE)
362 {
363 /* Write the end of tape. FIXME: we can't use write_eot here,
364 as it gets confused when the input is at end of file. */
365
366 idx_t total_zero_blocks = 0;
367
368 do
369 {
370 idx_t zero_blocks = blocking_factor - new_blocks;
371 memset (new_record + new_blocks, 0, BLOCKSIZE * zero_blocks);
372 total_zero_blocks += zero_blocks;
373 write_record (total_zero_blocks < 2);
374 }
375 while (total_zero_blocks < 2);
376 }
377
378 if (! acting_as_filter && ! _isrmt (archive))
379 {
380 if (sys_truncate (archive) < 0)
381 truncate_warn (archive_name_array[0]);
382 }
383 }
384 free (new_record);
385
386 close_archive ();
387 names_notfound ();
388}