]>
Commit | Line | Data |
---|---|---|
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 | ||
25 | static union block *new_record; | |
26 | static idx_t new_blocks; | |
27 | static 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. */ | |
31 | off_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! */ | |
37 | static void | |
38 | move_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. */ | |
72 | static void | |
73 | write_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 | ||
103 | static void | |
104 | write_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 | ||
114 | static void | |
115 | write_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 | ||
129 | static void | |
130 | flush_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 | ||
147 | void | |
148 | delete_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 (¤t_header, | |
166 | ¤t_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 (¤t_header, ¤t_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 (¤t_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 (¤t_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 | } |