Keep a detailed map of archive members stored in the record buffer.

A separate map (bufmap) provides information for creating
multi-volume continuation headers.

* src/buffer.c (bufmap): New struct.
(bufmap_head, bufmap_tail, inhibit_map): New variables.
(mv_begin_write): New function.
(mv_begin): Rename to mv_begin_read. Rewrite using mv_begin_write.
All callers changed.
(mv_total_size): Remove.
(bufmap_locate, bufmap_free, bufmap_reset): New functions.
(_flush_write): Update bufmap.
(close_archive): Free bufmap.
(add_chunk_header): Take a bufmap argument.
(gnu_add_multi_volume_header): Likewise.
(add_multi_volume_header): Likewise.
(_gnu_flush_write): Rewrite using bufmap.
(real_s_name, real_s_totsize)
(real_s_sizeleft)
(save_name, save_totsize, save_sizeleft): Removed. All
uses updated.
(mv_size_left): Update bufmap_head.
(mv_end): Rewrite.
(multi_volume_sync): Remove.

* src/common.h (mv_begin_write): New prototype.
(mv_begin): Rename to mv_begin_read.
* src/create.c: Use mv_begin_write instead of mv_begin.
Remove calls to mv_size_left and mv_end.
* src/sparse.c: Likewise.

* tests/multiv07.at: Close stdin.
* tests/spmvp00.at: Update AT_KEYWORDS.
* tests/spmvp10.at: Likewise.

* tests/multiv08.at: New testcase.
* tests/Makefile.am, tests/testsuite.at: Add multiv08.at.
This commit is contained in:
Sergey Poznyakoff
2010-07-11 19:56:45 +03:00
parent b4bcb97e38
commit ab6dd4948d
14 changed files with 219 additions and 133 deletions

View File

@@ -76,8 +76,7 @@ static bool read_full_records = false;
/* We're reading, but we just read the last block and it's time to update.
Declared in update.c
As least EXTERN like this one as possible. (?? --gray)
FIXME: Either eliminate it or move it to common.h.
*/
extern bool time_to_start_writing;
@@ -101,19 +100,94 @@ static int global_volno = 1; /* volume number to print in external
bool write_archive_to_stdout;
/* Used by flush_read and flush_write to store the real info about saved
names. */
static char *real_s_name;
static off_t real_s_totsize;
static off_t real_s_sizeleft;
/* Multi-volume tracking support */
static char *save_name; /* name of the file we are currently writing */
static off_t save_totsize; /* total size of file we are writing, only
valid if save_name is nonzero */
static off_t save_sizeleft; /* where we are in the file we are writing,
only valid if save_name is nonzero */
/* When creating a multi-volume archive, each `bufmap' represents
a member stored (perhaps partly) in the current record buffer.
After flushing the record to the output media, all bufmaps that
represent fully written members are removed from the list, then
the sizeleft and start numbers in the remaining bufmaps are updated.
When reading from a multi-volume archive, the list degrades to a
single element, which keeps information about the member currently
being read.
*/
struct bufmap
{
struct bufmap *next; /* Pointer to the next map entry */
size_t start; /* Offset of the first data block */
char *file_name; /* Name of the stored file */
off_t sizetotal; /* Size of the stored file */
off_t sizeleft; /* Size left to read/write */
};
static struct bufmap *bufmap_head, *bufmap_tail;
/* This variable, when set, inhibits updating the bufmap chain after
a write. This is necessary when writing extended POSIX headers. */
static int inhibit_map;
void
mv_begin_write (const char *file_name, off_t totsize, off_t sizeleft)
{
if (multi_volume_option)
{
struct bufmap *bp = xmalloc (sizeof bp[0]);
if (bufmap_tail)
bufmap_tail->next = bp;
else
bufmap_head = bp;
bufmap_tail = bp;
bp->next = NULL;
bp->start = current_block - record_start;
bp->file_name = xstrdup (file_name);
bp->sizetotal = totsize;
bp->sizeleft = sizeleft;
}
}
static struct bufmap *
bufmap_locate (size_t off)
{
struct bufmap *map;
for (map = bufmap_head; map; map = map->next)
{
if (!map->next
|| off < map->next->start * BLOCKSIZE)
break;
}
return map;
}
static void
bufmap_free (struct bufmap *mark)
{
struct bufmap *map;
for (map = bufmap_head; map && map != mark; )
{
struct bufmap *next = map->next;
free (map->file_name);
free (map);
map = next;
}
bufmap_head = map;
if (!bufmap_head)
bufmap_tail = bufmap_head;
}
static void
bufmap_reset (struct bufmap *map, ssize_t fixup)
{
bufmap_free (map);
if (map)
{
for (; map; map = map->next)
map->start += fixup;
}
}
static struct tar_stat_info dummy;
@@ -125,32 +199,23 @@ buffer_write_global_xheader ()
}
void
mv_begin (struct tar_stat_info *st)
mv_begin_read (struct tar_stat_info *st)
{
if (multi_volume_option)
{
assign_string (&save_name, st->orig_file_name);
save_totsize = save_sizeleft = st->stat.st_size;
}
mv_begin_write (st->orig_file_name, st->stat.st_size, st->stat.st_size);
}
void
mv_end ()
{
if (multi_volume_option)
assign_string (&save_name, 0);
}
void
mv_total_size (off_t size)
{
save_totsize = size;
bufmap_free (NULL);
}
void
mv_size_left (off_t size)
{
save_sizeleft = size;
if (bufmap_head)
bufmap_head->sizeleft = size;
}
@@ -511,9 +576,7 @@ _open_archive (enum access_mode wanted_access)
FATAL_ERROR ((0, 0, _("No archive name given")));
tar_stat_destroy (&current_stat_info);
save_name = 0;
real_s_name = 0;
record_index = 0;
init_buffer ();
@@ -673,6 +736,20 @@ _flush_write (void)
else
status = sys_write_archive_buffer ();
if (status && multi_volume_option && !inhibit_map)
{
struct bufmap *map = bufmap_locate (status);
if (map)
{
size_t delta = status - map->start * BLOCKSIZE;
if (delta > map->sizeleft)
delta = map->sizeleft;
map->sizeleft -= delta;
if (map->sizeleft == 0)
map = map->next;
bufmap_reset (map, map ? (- map->start) : 0);
}
}
return status;
}
@@ -907,12 +984,9 @@ close_archive (void)
sys_wait_for_child (child_pid, hit_eof);
tar_stat_destroy (&current_stat_info);
if (save_name)
free (save_name);
if (real_s_name)
free (real_s_name);
free (record_buffer[0]);
free (record_buffer[1]);
bufmap_free (NULL);
}
/* Called to initialize the global volume number. */
@@ -1280,30 +1354,30 @@ try_new_volume (void)
break;
}
if (real_s_name)
if (bufmap_head)
{
uintmax_t s;
if (!continued_file_name
|| strcmp (continued_file_name, real_s_name))
|| strcmp (continued_file_name, bufmap_head->file_name))
{
if ((archive_format == GNU_FORMAT || archive_format == OLDGNU_FORMAT)
&& strlen (real_s_name) >= NAME_FIELD_SIZE
&& strncmp (continued_file_name, real_s_name,
&& strlen (bufmap_head->file_name) >= NAME_FIELD_SIZE
&& strncmp (continued_file_name, bufmap_head->file_name,
NAME_FIELD_SIZE) == 0)
WARN ((0, 0,
_("%s is possibly continued on this volume: header contains truncated name"),
quote (real_s_name)));
quote (bufmap_head->file_name)));
else
{
WARN ((0, 0, _("%s is not continued on this volume"),
quote (real_s_name)));
quote (bufmap_head->file_name)));
return false;
}
}
s = continued_file_size + continued_file_offset;
if (real_s_totsize != s || s < continued_file_offset)
if (bufmap_head->sizetotal != s || s < continued_file_offset)
{
char totsizebuf[UINTMAX_STRSIZE_BOUND];
char s1buf[UINTMAX_STRSIZE_BOUND];
@@ -1311,21 +1385,22 @@ try_new_volume (void)
WARN ((0, 0, _("%s is the wrong size (%s != %s + %s)"),
quote (continued_file_name),
STRINGIFY_BIGINT (save_totsize, totsizebuf),
STRINGIFY_BIGINT (bufmap_head->sizetotal, totsizebuf),
STRINGIFY_BIGINT (continued_file_size, s1buf),
STRINGIFY_BIGINT (continued_file_offset, s2buf)));
return false;
}
if (real_s_totsize - real_s_sizeleft != continued_file_offset)
if (bufmap_head->sizetotal - bufmap_head->sizeleft !=
continued_file_offset)
{
char totsizebuf[UINTMAX_STRSIZE_BOUND];
char s1buf[UINTMAX_STRSIZE_BOUND];
char s2buf[UINTMAX_STRSIZE_BOUND];
WARN ((0, 0, _("This volume is out of sequence (%s - %s != %s)"),
STRINGIFY_BIGINT (real_s_totsize, totsizebuf),
STRINGIFY_BIGINT (real_s_sizeleft, s1buf),
STRINGIFY_BIGINT (bufmap_head->sizetotal, totsizebuf),
STRINGIFY_BIGINT (bufmap_head->sizeleft, s1buf),
STRINGIFY_BIGINT (continued_file_offset, s2buf)));
return false;
@@ -1477,26 +1552,24 @@ add_volume_label (void)
}
static void
add_chunk_header (void)
add_chunk_header (struct bufmap *map)
{
if (archive_format == POSIX_FORMAT)
{
off_t block_ordinal;
union block *blk;
struct tar_stat_info st;
static size_t real_s_part_no; /* FIXME */
real_s_part_no++;
memset (&st, 0, sizeof st);
st.orig_file_name = st.file_name = real_s_name;
st.orig_file_name = st.file_name = map->file_name;
st.stat.st_mode = S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
st.stat.st_uid = getuid ();
st.stat.st_gid = getgid ();
st.orig_file_name = xheader_format_name (&st,
"%d/GNUFileParts.%p/%f.%n",
real_s_part_no);
volno);
st.file_name = st.orig_file_name;
st.archive_file_size = st.stat.st_size = real_s_sizeleft;
st.archive_file_size = st.stat.st_size = map->sizeleft;
block_ordinal = current_block_ordinal ();
blk = start_header (&st);
@@ -1520,27 +1593,23 @@ write_volume_label (void)
/* Write GNU multi-volume header */
static void
gnu_add_multi_volume_header (void)
gnu_add_multi_volume_header (struct bufmap *map)
{
int tmp;
union block *block = find_next_block ();
if (strlen (real_s_name) > NAME_FIELD_SIZE)
if (strlen (map->file_name) > NAME_FIELD_SIZE)
WARN ((0, 0,
_("%s: file name too long to be stored in a GNU multivolume header, truncated"),
quotearg_colon (real_s_name)));
quotearg_colon (map->file_name)));
memset (block, 0, BLOCKSIZE);
/* FIXME: Michael P Urban writes: [a long name file] is being written
when a new volume rolls around [...] Looks like the wrong value is
being preserved in real_s_name, though. */
strncpy (block->header.name, real_s_name, NAME_FIELD_SIZE);
strncpy (block->header.name, map->file_name, NAME_FIELD_SIZE);
block->header.typeflag = GNUTYPE_MULTIVOL;
OFF_TO_CHARS (real_s_sizeleft, block->header.size);
OFF_TO_CHARS (real_s_totsize - real_s_sizeleft,
OFF_TO_CHARS (map->sizeleft, block->header.size);
OFF_TO_CHARS (map->sizetotal - map->sizeleft,
block->oldgnu_header.offset);
tmp = verbose_option;
@@ -1553,40 +1622,17 @@ gnu_add_multi_volume_header (void)
/* Add a multi volume header to the current archive. The exact header format
depends on the archive format. */
static void
add_multi_volume_header (void)
add_multi_volume_header (struct bufmap *map)
{
if (archive_format == POSIX_FORMAT)
{
off_t d = real_s_totsize - real_s_sizeleft;
xheader_store ("GNU.volume.filename", &dummy, real_s_name);
xheader_store ("GNU.volume.size", &dummy, &real_s_sizeleft);
off_t d = map->sizetotal - map->sizeleft;
xheader_store ("GNU.volume.filename", &dummy, map->file_name);
xheader_store ("GNU.volume.size", &dummy, &map->sizeleft);
xheader_store ("GNU.volume.offset", &dummy, &d);
}
else
gnu_add_multi_volume_header ();
}
/* Synchronize multi-volume globals */
static void
multi_volume_sync (void)
{
if (multi_volume_option)
{
if (save_name)
{
assign_string (&real_s_name,
safer_name_suffix (save_name, false,
absolute_names_option));
real_s_totsize = save_totsize;
real_s_sizeleft = save_sizeleft;
}
else
{
assign_string (&real_s_name, 0);
real_s_totsize = 0;
real_s_sizeleft = 0;
}
}
gnu_add_multi_volume_header (map);
}
@@ -1673,8 +1719,6 @@ _gnu_flush_read (void)
archive_write_error (status);
}
multi_volume_sync ();
for (;;)
{
status = rmtread (archive, record_start->buffer, record_size);
@@ -1726,8 +1770,8 @@ _gnu_flush_write (size_t buffer_level)
char *copy_ptr;
size_t copy_size;
size_t bufsize;
tarlong wrt;
struct bufmap *map;
status = _flush_write ();
if (status != record_size && !multi_volume_option)
archive_write_error (status);
@@ -1740,10 +1784,11 @@ _gnu_flush_write (size_t buffer_level)
if (status == record_size)
{
multi_volume_sync ();
return;
}
map = bufmap_locate (status);
if (status % BLOCKSIZE)
{
ERROR ((0, 0, _("write did not end on a block boundary")));
@@ -1755,7 +1800,6 @@ _gnu_flush_write (size_t buffer_level)
if (status < 0 && errno != ENOSPC && errno != EIO && errno != ENXIO)
archive_write_error (status);
real_s_sizeleft -= status;
if (!new_volume (ACCESS_WRITE))
return;
@@ -1767,25 +1811,28 @@ _gnu_flush_write (size_t buffer_level)
copy_ptr = record_start->buffer + status;
copy_size = buffer_level - status;
/* Switch to the next buffer */
record_index = !record_index;
init_buffer ();
inhibit_map = 1;
if (volume_label_option)
add_volume_label ();
if (real_s_name)
add_multi_volume_header ();
if (map)
add_multi_volume_header (map);
write_extended (true, &dummy, find_next_block ());
tar_stat_destroy (&dummy);
if (real_s_name)
add_chunk_header ();
wrt = bytes_written;
if (map)
add_chunk_header (map);
header = find_next_block ();
bufmap_reset (map, header - record_start);
bufsize = available_space_after (header);
inhibit_map = 0;
while (bufsize < copy_size)
{
memcpy (header->buffer, copy_ptr, bufsize);
@@ -1798,16 +1845,6 @@ _gnu_flush_write (size_t buffer_level)
memcpy (header->buffer, copy_ptr, copy_size);
memset (header->buffer + copy_size, 0, bufsize - copy_size);
set_next_block_after (header + (copy_size - 1) / BLOCKSIZE);
if (multi_volume_option && wrt < bytes_written)
{
/* The value of bytes_written has changed while moving data;
that means that flush_archive was executed at least once in
between, and, as a consequence, copy_size bytes were not written
to disk. We need to update sizeleft variables to compensate for
that. */
save_sizeleft += copy_size;
multi_volume_sync ();
}
find_next_block ();
}

View File

@@ -427,9 +427,10 @@ void archive_read_error (void);
off_t seek_archive (off_t size);
void set_start_time (void);
void mv_begin (struct tar_stat_info *st);
void mv_begin_write (const char *file_name, off_t totsize, off_t sizeleft);
void mv_begin_read (struct tar_stat_info *st);
void mv_end (void);
void mv_total_size (off_t size);
void mv_size_left (off_t size);
void buffer_write_global_xheader (void);

View File

@@ -122,7 +122,7 @@ read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *))
size_t data_size;
off_t size = st->stat.st_size;
mv_begin (st);
mv_begin_read (st);
while (size)
{
data_block = find_next_block ();

View File

@@ -1012,7 +1012,6 @@ pad_archive (off_t size_left)
union block *blk;
while (size_left > 0)
{
mv_size_left (size_left);
blk = find_next_block ();
memset (blk->buffer, 0, BLOCKSIZE);
set_next_block_after (blk);
@@ -1038,13 +1037,11 @@ dump_regular_file (int fd, struct tar_stat_info *st)
finish_header (st, blk, block_ordinal);
mv_begin (st);
mv_begin_write (st->file_name, st->stat.st_size, st->stat.st_size);
while (size_left > 0)
{
size_t bufsize, count;
mv_size_left (size_left);
blk = find_next_block ();
bufsize = available_space_after (blk);
@@ -1138,11 +1135,9 @@ dump_dir0 (char *directory,
p_buffer = buffer;
size_left = totsize;
mv_begin (st);
mv_total_size (totsize);
mv_begin_write (st->file_name, totsize, totsize);
while (size_left > 0)
{
mv_size_left (size_left);
blk = find_next_block ();
bufsize = available_space_after (blk);
if (size_left < bufsize)
@@ -1157,7 +1152,6 @@ dump_dir0 (char *directory,
p_buffer += bufsize;
set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
}
mv_end ();
}
return;
}
@@ -1619,7 +1613,6 @@ dump_file0 (struct tar_stat_info *st, const char *p,
{
case dump_status_ok:
case dump_status_short:
mv_end ();
file_count_links (st);
break;

View File

@@ -812,7 +812,7 @@ extract_file (char *file_name, int typeflag)
}
}
mv_begin (&current_stat_info);
mv_begin_read (&current_stat_info);
if (current_stat_info.is_sparse)
sparse_extract_file (fd, &current_stat_info, &size);
else

View File

@@ -1452,7 +1452,7 @@ get_gnu_dumpdir (struct tar_stat_info *stat_info)
to = archive_dir;
set_next_block_after (current_header);
mv_begin (stat_info);
mv_begin_read (stat_info);
for (; size > 0; size -= copied)
{

View File

@@ -1353,7 +1353,7 @@ skip_file (off_t size)
{
union block *x;
/* FIXME: Make sure mv_begin is always called before it */
/* FIXME: Make sure mv_begin_read is always called before it */
if (seekable_archive)
{
@@ -1388,7 +1388,7 @@ skip_member (void)
char save_typeflag = current_header->header.typeflag;
set_next_block_after (current_header);
mv_begin (&current_stat_info);
mv_begin_read (&current_stat_info);
if (current_stat_info.is_sparse)
sparse_skip_file (&current_stat_info);

View File

@@ -324,7 +324,6 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i)
memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read);
bytes_left -= bytes_read;
file->dumped_size += bytes_read;
mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
set_next_block_after (blk);
}
@@ -398,10 +397,11 @@ sparse_dump_file (int fd, struct tar_stat_info *st)
{
size_t i;
mv_begin (file.stat_info);
mv_begin_write (file.stat_info->file_name,
file.stat_info->stat.st_size,
file.stat_info->archive_file_size - file.dumped_size);
for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
rc = tar_sparse_dump_region (&file, i);
mv_end ();
}
}
@@ -566,7 +566,7 @@ sparse_diff_file (int fd, struct tar_stat_info *st)
file.seekable = true; /* File *must* be seekable for compare to work */
rc = tar_sparse_decode_header (&file);
mv_begin (st);
mv_begin_read (st);
for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
{
rc = check_sparse_region (&file,