/* When creating a multi-volume archive, each 'bufmap' represents
a member stored (perhaps partly) in the current record buffer.
+ Bufmaps are form a single-linked list in chronological order.
+
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.
+ represent fully written members are removed from the list, the
+ nblocks and sizeleft values in the bufmap_head and start values
+ in all remaining bufmaps are updated. The information stored
+ in bufmap_head is used to form the volume header.
When reading from a multi-volume archive, the list degrades to a
single element, which keeps information about the member currently
- being read.
+ being read. In that case the sizeleft member is updated explicitly
+ from the extractor code by calling the mv_size_left function. The
+ information from bufmap_head is compared with the volume header data
+ to ensure that subsequent volumes are fed in the right order.
*/
struct bufmap
char *file_name; /* Name of the stored file */
off_t sizetotal; /* Size of the stored file */
off_t sizeleft; /* Size left to read/write */
+ size_t nblocks; /* Number of blocks written since reset */
};
static struct bufmap *bufmap_head, *bufmap_tail;
bp->file_name = xstrdup (file_name);
bp->sizetotal = totsize;
bp->sizeleft = sizeleft;
+ bp->nblocks = 0;
}
}
for (map = bufmap_head; map; map = map->next)
{
- if (!map->next
- || off < map->next->start * BLOCKSIZE)
+ if (!map->next || off < map->next->start * BLOCKSIZE)
break;
}
return map;
if (map)
{
for (; map; map = map->next)
- map->start += fixup;
+ {
+ map->start += fixup;
+ map->nblocks = 0;
+ }
}
}
if (map)
{
size_t delta = status - map->start * BLOCKSIZE;
+ ssize_t diff;
+ map->nblocks += delta / 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);
+ {
+ diff = map->start + map->nblocks;
+ map = map->next;
+ }
+ else
+ diff = map->start;
+ bufmap_reset (map, - diff);
}
}
return status;
{
if (time_to_start_writing || access_mode == ACCESS_WRITE)
{
- flush_archive ();
- if (current_block > record_start)
- flush_archive ();
+ do
+ flush_archive ();
+ while (current_block > record_start);
}
compute_duration ();
{
if (archive_format == POSIX_FORMAT)
{
- off_t block_ordinal;
union block *blk;
struct tar_stat_info st;
st.file_name = st.orig_file_name;
st.archive_file_size = st.stat.st_size = map->sizeleft;
- block_ordinal = current_block_ordinal ();
blk = start_header (&st);
if (!blk)
abort (); /* FIXME */
- finish_header (&st, blk, block_ordinal);
+ simple_finish_header (write_extended (false, &st, blk));
free (st.orig_file_name);
}
}
--- /dev/null
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+# Test suite for GNU tar.
+# Copyright 2015-2017 Free Software Foundation, Inc.
+#
+# 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/>.
+
+# Description: When creating multivolume archives, the bufmap code in
+# buffer.c implicitly assumed that the members are stored in the archive
+# in a contiguous fashion, ignoring the member (and eventual extended) headers
+# between them. This worked until the member header happened to be at
+# the very beginning of the volume, in which case its length was included in
+# the calculation of the stored size and size left to store. Due to this,
+# the GNUFileParts extended header contained invalid GNU.volume.offset value,
+# and the resulting archive failed to extract properly. The bug affected
+# versions of tar up to 1.29.90 (commit da8d0659a6).
+#
+# This test case also checks that GNUFileParts headers are not displayed in
+# verbose mode.
+#
+# Reported by: <russiangolem@gmail.com>
+# References:
+# <CAE7Kiz_0oMqGdzkoh0FbOd=hUoPhtHHYhjZveM_4hEku081QFQ@mail.gmail.com>,
+# http://lists.gnu.org/archive/html/bug-tar/2017-05/msg00007.html
+#
+
+AT_SETUP([file start at the beginning of a posix volume])
+AT_KEYWORDS([multivolume multiv multiv10])
+
+AT_TAR_CHECK([
+set -e
+genfile --length=15360 --file data1
+genfile --length=15360 --file data2
+tar -v -c -L 10 -M -f 1.tar -f 2.tar -f 3.tar -f 4.tar -f 5.tar data1 data2
+tar -M -t -f 1.tar -f 2.tar -f 3.tar -f 4.tar -f 5.tar
+mkdir out
+tar -C out -M -x -f 1.tar -f 2.tar -f 3.tar -f 4.tar -f 5.tar
+cmp data1 out/data1
+cmp data2 out/data2
+],
+[0],
+[data1
+data2
+data1
+data2
+],
+[],[],[],[posix])
+
+AT_CLEANUP