mirror of
https://git.savannah.gnu.org/git/tar.git
synced 2026-08-17 14:56:02 +00:00
UPDATE_COPYRIGHT_USE_INTERVALS=1 \
$HOME/src/gnu/gnulib/build-aux/update-copyright \
$(git ls-files | sed -e '/^gnulib$/d
/^paxutils$/d
/^COPYING$/d
/\/fdl.texi$/d')
sed -i '2000,${
/^Copyright @copyright/d
s/^[0-9]*--\(2026 Free Software Foundation, Inc.\)/Copyright (C) \1/
}' doc/tar.texi
389 lines
9.7 KiB
C
389 lines
9.7 KiB
C
/* Delete entries from a tar archive.
|
|
|
|
Copyright 1988-2026 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU tar.
|
|
|
|
GNU tar is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GNU tar is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include <system.h>
|
|
|
|
#include "common.h"
|
|
#include <rmt.h>
|
|
|
|
static union block *new_record;
|
|
static idx_t new_blocks;
|
|
static bool acting_as_filter;
|
|
|
|
/* The number of records skipped at the start of the archive, when
|
|
passing over members that are not deleted. */
|
|
off_t records_skipped;
|
|
|
|
/* Move archive descriptor by COUNT records worth. If COUNT is
|
|
positive we move forward, else we move negative. If it's a tape,
|
|
MTIOCTOP had better work. If it's something else, we try to seek
|
|
on it. If we can't seek, we lose! */
|
|
static void
|
|
move_archive (off_t count)
|
|
{
|
|
if (count == 0)
|
|
return;
|
|
|
|
if (mtioseek (false, count))
|
|
return;
|
|
|
|
off_t position0 = rmtlseek (archive, 0, SEEK_CUR), position = 0;
|
|
if (0 <= position0)
|
|
{
|
|
/* Pretend the starting position is at the first record
|
|
boundary after POSITION0. This is useful at EOF after
|
|
a short read. */
|
|
idx_t short_size = position0 % record_size;
|
|
idx_t start_offset = short_size ? record_size - short_size : 0;
|
|
off_t increment, move_start;
|
|
if (ckd_mul (&increment, record_size, count)
|
|
|| ckd_add (&move_start, position0, start_offset)
|
|
|| ckd_add (&position, move_start, increment)
|
|
|| position < 0)
|
|
{
|
|
paxerror (EOVERFLOW, "lseek: %s", archive_name_array[0]);
|
|
return;
|
|
}
|
|
else if (rmtlseek (archive, position, SEEK_SET) == position)
|
|
return;
|
|
}
|
|
if (!_isrmt (archive))
|
|
seek_error_details (archive_name_array[0], position);
|
|
}
|
|
|
|
/* Write out the record which has been filled. If MOVE_BACK_FLAG,
|
|
backspace to where we started. */
|
|
static void
|
|
write_record (bool move_back_flag)
|
|
{
|
|
union block *save_record = record_start;
|
|
record_start = new_record;
|
|
|
|
if (acting_as_filter)
|
|
{
|
|
archive = STDOUT_FILENO;
|
|
flush_write ();
|
|
archive = STDIN_FILENO;
|
|
}
|
|
else
|
|
{
|
|
move_archive ((records_written + records_skipped) - records_read);
|
|
flush_write ();
|
|
}
|
|
|
|
record_start = save_record;
|
|
|
|
if (move_back_flag)
|
|
{
|
|
/* Move the tape head back to where we were. */
|
|
|
|
if (! acting_as_filter)
|
|
move_archive (records_read - (records_written + records_skipped));
|
|
}
|
|
|
|
new_blocks = 0;
|
|
}
|
|
|
|
static void
|
|
write_recent_blocks (union block *h, idx_t blocks)
|
|
{
|
|
for (idx_t i = 0; i < blocks; i++)
|
|
{
|
|
new_record[new_blocks++] = h[i];
|
|
if (new_blocks == blocking_factor)
|
|
write_record (true);
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_recent_bytes (char *data, idx_t bytes)
|
|
{
|
|
idx_t blocks = bytes >> LG_BLOCKSIZE;
|
|
idx_t rest = bytes & (BLOCKSIZE - 1);
|
|
|
|
write_recent_blocks ((union block *)data, blocks);
|
|
memcpy (new_record[new_blocks].buffer, data + blocks * BLOCKSIZE, rest);
|
|
if (rest < BLOCKSIZE)
|
|
memset (new_record[new_blocks].buffer + rest, 0, BLOCKSIZE - rest);
|
|
new_blocks++;
|
|
if (new_blocks == blocking_factor)
|
|
write_record (true);
|
|
}
|
|
|
|
static void
|
|
flush_file (void)
|
|
{
|
|
set_next_block_after (current_header);
|
|
off_t size = current_stat_info.stat.st_size;
|
|
off_t blocks_to_skip = (size >> LG_BLOCKSIZE) + !!(size & (BLOCKSIZE - 1));
|
|
|
|
while (record_end - current_block <= blocks_to_skip)
|
|
{
|
|
blocks_to_skip -= (record_end - current_block);
|
|
flush_archive ();
|
|
if (record_end == current_block)
|
|
/* Hit EOF */
|
|
return;
|
|
}
|
|
current_block += blocks_to_skip;
|
|
}
|
|
|
|
void
|
|
delete_archive_members (void)
|
|
{
|
|
enum read_header logical_status = HEADER_STILL_UNREAD;
|
|
enum read_header previous_status = HEADER_STILL_UNREAD;
|
|
|
|
/* FIXME: Should clean the routine before cleaning these variables :-( */
|
|
struct name *name;
|
|
off_t blocks_to_keep = 0;
|
|
ptrdiff_t kept_blocks_in_record;
|
|
|
|
name_gather ();
|
|
open_archive (ACCESS_UPDATE);
|
|
acting_as_filter = streq (archive_name_array[0], "-");
|
|
|
|
/* Skip to the first member that matches the name list. */
|
|
do
|
|
{
|
|
enum read_header status = read_header (¤t_header,
|
|
¤t_stat_info,
|
|
read_header_x_raw);
|
|
|
|
switch (status)
|
|
{
|
|
case HEADER_STILL_UNREAD:
|
|
abort ();
|
|
|
|
case HEADER_SUCCESS:
|
|
if ((name = name_scan (current_stat_info.file_name, false)) == NULL)
|
|
{
|
|
skim_member (acting_as_filter);
|
|
break;
|
|
}
|
|
name->found_count++;
|
|
if (!isfound (name))
|
|
{
|
|
skim_member (acting_as_filter);
|
|
break;
|
|
}
|
|
FALLTHROUGH;
|
|
case HEADER_SUCCESS_EXTENDED:
|
|
logical_status = status;
|
|
break;
|
|
|
|
case HEADER_ZERO_BLOCK:
|
|
if (ignore_zeros_option)
|
|
{
|
|
set_next_block_after (current_header);
|
|
break;
|
|
}
|
|
FALLTHROUGH;
|
|
case HEADER_END_OF_FILE:
|
|
logical_status = HEADER_END_OF_FILE;
|
|
break;
|
|
|
|
case HEADER_FAILURE:
|
|
set_next_block_after (current_header);
|
|
switch (previous_status)
|
|
{
|
|
case HEADER_STILL_UNREAD:
|
|
paxwarn (0, _("This does not look like a tar archive"));
|
|
FALLTHROUGH;
|
|
case HEADER_SUCCESS:
|
|
case HEADER_SUCCESS_EXTENDED:
|
|
case HEADER_ZERO_BLOCK:
|
|
paxerror (0, _("Skipping to next header"));
|
|
FALLTHROUGH;
|
|
case HEADER_FAILURE:
|
|
break;
|
|
|
|
case HEADER_END_OF_FILE:
|
|
abort ();
|
|
}
|
|
break;
|
|
}
|
|
|
|
previous_status = status;
|
|
}
|
|
while (logical_status == HEADER_STILL_UNREAD);
|
|
|
|
records_skipped = records_read - 1;
|
|
new_record = xmalloc (record_size);
|
|
|
|
if (logical_status == HEADER_SUCCESS
|
|
|| logical_status == HEADER_SUCCESS_EXTENDED)
|
|
{
|
|
write_archive_to_stdout = false;
|
|
|
|
/* Save away blocks before this one in this record. */
|
|
|
|
new_blocks = current_block - record_start;
|
|
if (new_blocks)
|
|
memcpy (new_record, record_start, new_blocks * BLOCKSIZE);
|
|
|
|
if (logical_status == HEADER_SUCCESS)
|
|
{
|
|
logical_status = HEADER_STILL_UNREAD;
|
|
flush_file ();
|
|
}
|
|
|
|
/* Skip matching members and move the rest up the archive. */
|
|
while (logical_status != HEADER_END_OF_FILE)
|
|
{
|
|
enum read_header status;
|
|
|
|
/* Fill in a record. */
|
|
|
|
if (current_block == record_end)
|
|
flush_archive ();
|
|
|
|
status = read_header (¤t_header, ¤t_stat_info,
|
|
read_header_auto);
|
|
|
|
switch (status)
|
|
{
|
|
case HEADER_STILL_UNREAD:
|
|
case HEADER_SUCCESS_EXTENDED:
|
|
abort ();
|
|
|
|
case HEADER_SUCCESS:
|
|
/* Found another header. */
|
|
xheader_decode (¤t_stat_info);
|
|
|
|
if ((name = name_scan (current_stat_info.file_name, false)) != NULL)
|
|
{
|
|
name->found_count++;
|
|
if (isfound (name))
|
|
{
|
|
flush_file ();
|
|
break;
|
|
}
|
|
}
|
|
/* Copy header. */
|
|
|
|
if (current_stat_info.xhdr.size)
|
|
{
|
|
write_recent_bytes (current_stat_info.xhdr.buffer,
|
|
current_stat_info.xhdr.size);
|
|
}
|
|
else
|
|
{
|
|
write_recent_blocks (recent_long_name,
|
|
recent_long_name_blocks);
|
|
write_recent_blocks (recent_long_link,
|
|
recent_long_link_blocks);
|
|
}
|
|
new_record[new_blocks] = *current_header;
|
|
new_blocks++;
|
|
blocks_to_keep
|
|
= ((current_stat_info.stat.st_size >> LG_BLOCKSIZE)
|
|
+ !!(current_stat_info.stat.st_size & (BLOCKSIZE - 1)));
|
|
set_next_block_after (current_header);
|
|
if (new_blocks == blocking_factor)
|
|
write_record (true);
|
|
|
|
/* Copy data. */
|
|
|
|
kept_blocks_in_record = record_end - current_block;
|
|
if (kept_blocks_in_record > blocks_to_keep)
|
|
kept_blocks_in_record = blocks_to_keep;
|
|
|
|
while (blocks_to_keep)
|
|
{
|
|
ptrdiff_t count;
|
|
|
|
if (current_block == record_end)
|
|
{
|
|
flush_read ();
|
|
current_block = record_start;
|
|
kept_blocks_in_record = blocking_factor;
|
|
if (kept_blocks_in_record > blocks_to_keep)
|
|
kept_blocks_in_record = blocks_to_keep;
|
|
}
|
|
count = kept_blocks_in_record;
|
|
if (blocking_factor - new_blocks < count)
|
|
count = blocking_factor - new_blocks;
|
|
|
|
if (! count)
|
|
abort ();
|
|
|
|
memcpy (new_record + new_blocks, current_block,
|
|
count * BLOCKSIZE);
|
|
new_blocks += count;
|
|
current_block += count;
|
|
blocks_to_keep -= count;
|
|
kept_blocks_in_record -= count;
|
|
|
|
if (new_blocks == blocking_factor)
|
|
write_record (true);
|
|
}
|
|
break;
|
|
|
|
case HEADER_ZERO_BLOCK:
|
|
if (ignore_zeros_option)
|
|
set_next_block_after (current_header);
|
|
else
|
|
logical_status = HEADER_END_OF_FILE;
|
|
break;
|
|
|
|
case HEADER_END_OF_FILE:
|
|
logical_status = HEADER_END_OF_FILE;
|
|
break;
|
|
|
|
case HEADER_FAILURE:
|
|
paxerror (0, _("Deleting non-header from archive"));
|
|
set_next_block_after (current_header);
|
|
break;
|
|
|
|
default:
|
|
abort ();
|
|
}
|
|
tar_stat_destroy (¤t_stat_info);
|
|
}
|
|
|
|
if (logical_status == HEADER_END_OF_FILE)
|
|
{
|
|
/* Write the end of tape. FIXME: we can't use write_eot here,
|
|
as it gets confused when the input is at end of file. */
|
|
|
|
idx_t total_zero_blocks = 0;
|
|
|
|
do
|
|
{
|
|
idx_t zero_blocks = blocking_factor - new_blocks;
|
|
memset (new_record + new_blocks, 0, BLOCKSIZE * zero_blocks);
|
|
total_zero_blocks += zero_blocks;
|
|
write_record (total_zero_blocks < 2);
|
|
}
|
|
while (total_zero_blocks < 2);
|
|
}
|
|
|
|
if (! acting_as_filter && ! _isrmt (archive))
|
|
{
|
|
if (sys_truncate (archive) < 0)
|
|
truncate_warn (archive_name_array[0]);
|
|
}
|
|
}
|
|
free (new_record);
|
|
|
|
close_archive ();
|
|
names_notfound ();
|
|
}
|