|
@@ -1,6 +1,6 @@
|
|
|
/* Functions for dealing with sparse files
|
|
|
|
|
|
- Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
|
|
|
+ Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
|
under the terms of the GNU General Public License as published by the
|
|
@@ -740,11 +740,9 @@ oldgnu_dump_header (struct tar_sparse_file *file)
|
|
|
oldgnu_store_sparse_info (file, &i,
|
|
|
blk->sparse_header.sp,
|
|
|
SPARSES_IN_SPARSE_HEADER);
|
|
|
- set_next_block_after (blk);
|
|
|
if (i < file->stat_info->sparse_map_avail)
|
|
|
blk->sparse_header.isextended = 1;
|
|
|
- else
|
|
|
- break;
|
|
|
+ set_next_block_after (blk);
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
@@ -843,16 +841,16 @@ static struct tar_sparse_optab const star_optab = {
|
|
|
};
|
|
|
|
|
|
|
|
|
-/* GNU PAX sparse file format. The sparse file map is stored in
|
|
|
- x header:
|
|
|
+/* GNU PAX sparse file format. There are several versions:
|
|
|
+
|
|
|
+
|
|
|
+ * 0.0
|
|
|
+
|
|
|
+ The initial version of sparse format used by tar 1.14-1.15.1.
|
|
|
+ The sparse file map is stored in x header:
|
|
|
|
|
|
GNU.sparse.size Real size of the stored file
|
|
|
GNU.sparse.numblocks Number of blocks in the sparse map
|
|
|
- GNU.sparse.map Map of non-null data chunks. A string consisting
|
|
|
- of comma-separated values "offset,size[,offset,size]..."
|
|
|
-
|
|
|
- Tar versions 1.14-1.15.1 instead of the latter used:
|
|
|
-
|
|
|
repeat numblocks time
|
|
|
GNU.sparse.offset Offset of the next data block
|
|
|
GNU.sparse.numbytes Size of the next data block
|
|
@@ -860,26 +858,73 @@ static struct tar_sparse_optab const star_optab = {
|
|
|
|
|
|
This has been reported as conflicting with the POSIX specs. The reason is
|
|
|
that offsets and sizes of non-zero data blocks were stored in multiple
|
|
|
- instances of GNU.sparse.offset/GNU.sparse.numbytes variables. However,
|
|
|
+ instances of GNU.sparse.offset/GNU.sparse.numbytes variables, whereas
|
|
|
POSIX requires the latest occurrence of the variable to override all
|
|
|
previous occurrences.
|
|
|
-
|
|
|
- To avoid this incompatibility new keyword GNU.sparse.map was introduced
|
|
|
- in tar 1.15.2. Some people might still need the 1.14 way of handling
|
|
|
- sparse files for the compatibility reasons: it can be achieved by
|
|
|
- specifying `--pax-option delete=GNU.sparse.map' in the command line.
|
|
|
+
|
|
|
+ To avoid this incompatibility two following versions were introduced.
|
|
|
+
|
|
|
+ * 0.1
|
|
|
+
|
|
|
+ Used by tar 1.15.2 -- 1.15.91 (alpha releases).
|
|
|
+
|
|
|
+ The sparse file map is stored in
|
|
|
+ x header:
|
|
|
+
|
|
|
+ GNU.sparse.size Real size of the stored file
|
|
|
+ GNU.sparse.numblocks Number of blocks in the sparse map
|
|
|
+ GNU.sparse.map Map of non-null data chunks. A string consisting
|
|
|
+ of comma-separated values "offset,size[,offset,size]..."
|
|
|
+
|
|
|
+ The resulting GNU.sparse.map string can be *very* long. While POSIX does not
|
|
|
+ impose any limit on the length of a x header variable, this can confuse some
|
|
|
+ tars.
|
|
|
+
|
|
|
+ * 1.0
|
|
|
|
|
|
- See FIXME-1.14-1.15.1-1.20, below.
|
|
|
+ Starting from this version, the exact sparse format version is specified explicitely
|
|
|
+ in the header using the following variables:
|
|
|
+
|
|
|
+ GNU.sparse.major Major version
|
|
|
+ GNU.sparse.minor Minor version
|
|
|
+
|
|
|
+ X header keeps the following variables:
|
|
|
+
|
|
|
+ GNU.sparse.name Real file name of the sparse file
|
|
|
+ GNU.sparse.realsize Real size of the stored file (corresponds to the old
|
|
|
+ GNU.sparse.size variable)
|
|
|
+
|
|
|
+ The name field of the ustar header is constructed using the pattern
|
|
|
+ "%d/GNUSparseFile.%p/%f".
|
|
|
+
|
|
|
+ The sparse map itself is stored in the file data block, preceding the actual
|
|
|
+ file data. It consists of a series of octal numbers of arbitrary length, delimited
|
|
|
+ by newlines. The map is padded with nulls to the nearest block boundary.
|
|
|
+
|
|
|
+ The first number gives the number of entries in the map. Following are map entries,
|
|
|
+ each one consisting of two numbers giving the offset and size of the
|
|
|
+ data block it describes.
|
|
|
+
|
|
|
+ The format is designed in such a way that non-posix aware tars and tars not
|
|
|
+ supporting GNU.sparse.* keywords will extract each sparse file in its condensed
|
|
|
+ form with the file map attached and will place it into a separate directory.
|
|
|
+ Then, using a simple program it would be possible to expand the file to its
|
|
|
+ original form even without GNU tar.
|
|
|
+
|
|
|
+ Bu default, v.1.0 archives are created. To use other formats, --sparse-version
|
|
|
+ option is provided. Additionally, v.0.0 can be obtained by deleting GNU.sparse.map
|
|
|
+ from 0.1 format: --sparse-version 0.1 --pax-option delete=GNU.sparse.map
|
|
|
*/
|
|
|
|
|
|
static bool
|
|
|
pax_sparse_member_p (struct tar_sparse_file *file)
|
|
|
{
|
|
|
- return file->stat_info->sparse_map_avail > 0;
|
|
|
+ return file->stat_info->sparse_map_avail > 0
|
|
|
+ || file->stat_info->sparse_major > 0;
|
|
|
}
|
|
|
|
|
|
static bool
|
|
|
-pax_dump_header (struct tar_sparse_file *file)
|
|
|
+pax_dump_header_0 (struct tar_sparse_file *file)
|
|
|
{
|
|
|
off_t block_ordinal = current_block_ordinal ();
|
|
|
union block *blk;
|
|
@@ -892,13 +937,8 @@ pax_dump_header (struct tar_sparse_file *file)
|
|
|
xheader_store ("GNU.sparse.size", file->stat_info, NULL);
|
|
|
xheader_store ("GNU.sparse.numblocks", file->stat_info, NULL);
|
|
|
|
|
|
- /* FIXME-1.14-1.15.1-1.20: See the comment above.
|
|
|
- Starting with 1.17 this should display a warning about POSIX-incompatible
|
|
|
- keywords being generated. In 1.20, the true branch of the if block below
|
|
|
- will be removed and GNU.sparse.map will be marked in xhdr_tab as
|
|
|
- protected. */
|
|
|
-
|
|
|
- if (xheader_keyword_deleted_p ("GNU.sparse.map"))
|
|
|
+ if (xheader_keyword_deleted_p ("GNU.sparse.map")
|
|
|
+ || tar_sparse_minor == 0)
|
|
|
{
|
|
|
for (i = 0; i < file->stat_info->sparse_map_avail; i++)
|
|
|
{
|
|
@@ -936,13 +976,198 @@ pax_dump_header (struct tar_sparse_file *file)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+static bool
|
|
|
+pax_dump_header_1 (struct tar_sparse_file *file)
|
|
|
+{
|
|
|
+ off_t block_ordinal = current_block_ordinal ();
|
|
|
+ union block *blk;
|
|
|
+ char *p, *q;
|
|
|
+ size_t i;
|
|
|
+ char nbuf[UINTMAX_STRSIZE_BOUND];
|
|
|
+ off_t size = 0;
|
|
|
+ struct sp_array *map = file->stat_info->sparse_map;
|
|
|
+ char *save_file_name = file->stat_info->file_name;
|
|
|
+
|
|
|
+#define COPY_STRING(b,dst,src) do \
|
|
|
+ { \
|
|
|
+ char *endp = b->buffer + BLOCKSIZE; \
|
|
|
+ char *srcp = src; \
|
|
|
+ while (*srcp) \
|
|
|
+ { \
|
|
|
+ if (dst == endp) \
|
|
|
+ { \
|
|
|
+ set_next_block_after (b); \
|
|
|
+ b = find_next_block (); \
|
|
|
+ dst = b->buffer; \
|
|
|
+ endp = b->buffer + BLOCKSIZE; \
|
|
|
+ } \
|
|
|
+ *dst++ = *srcp++; \
|
|
|
+ } \
|
|
|
+ } while (0)
|
|
|
+
|
|
|
+ /* Compute stored file size */
|
|
|
+ p = umaxtostr (file->stat_info->sparse_map_avail, nbuf);
|
|
|
+ size += strlen (p) + 1;
|
|
|
+ for (i = 0; i < file->stat_info->sparse_map_avail; i++)
|
|
|
+ {
|
|
|
+ p = umaxtostr (map[i].offset, nbuf);
|
|
|
+ size += strlen (p) + 1;
|
|
|
+ p = umaxtostr (map[i].numbytes, nbuf);
|
|
|
+ size += strlen (p) + 1;
|
|
|
+ }
|
|
|
+ size = (size + BLOCKSIZE - 1) / BLOCKSIZE;
|
|
|
+ file->stat_info->archive_file_size += size * BLOCKSIZE;
|
|
|
+
|
|
|
+ /* Store sparse file identification */
|
|
|
+ xheader_store ("GNU.sparse.major", file->stat_info, NULL);
|
|
|
+ xheader_store ("GNU.sparse.minor", file->stat_info, NULL);
|
|
|
+ xheader_store ("GNU.sparse.name", file->stat_info, NULL);
|
|
|
+ xheader_store ("GNU.sparse.realsize", file->stat_info, NULL);
|
|
|
+
|
|
|
+ file->stat_info->file_name = xheader_format_name (file->stat_info,
|
|
|
+ "%d/GNUSparseFile.%p/%f", 0);
|
|
|
+
|
|
|
+ blk = start_header (file->stat_info);
|
|
|
+ /* Store the effective (shrunken) file size */
|
|
|
+ OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size);
|
|
|
+ finish_header (file->stat_info, blk, block_ordinal);
|
|
|
+ free (file->stat_info->file_name);
|
|
|
+ file->stat_info->file_name = save_file_name;
|
|
|
+
|
|
|
+ blk = find_next_block ();
|
|
|
+ q = blk->buffer;
|
|
|
+ p = umaxtostr (file->stat_info->sparse_map_avail, nbuf);
|
|
|
+ COPY_STRING (blk, q, p);
|
|
|
+ COPY_STRING (blk, q, "\n");
|
|
|
+ for (i = 0; i < file->stat_info->sparse_map_avail; i++)
|
|
|
+ {
|
|
|
+ p = umaxtostr (map[i].offset, nbuf);
|
|
|
+ COPY_STRING (blk, q, p);
|
|
|
+ COPY_STRING (blk, q, "\n");
|
|
|
+ p = umaxtostr (map[i].numbytes, nbuf);
|
|
|
+ COPY_STRING (blk, q, p);
|
|
|
+ COPY_STRING (blk, q, "\n");
|
|
|
+ }
|
|
|
+ memset (q, 0, BLOCKSIZE - (q - blk->buffer));
|
|
|
+ set_next_block_after (blk);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static bool
|
|
|
+pax_dump_header (struct tar_sparse_file *file)
|
|
|
+{
|
|
|
+ file->stat_info->sparse_major = tar_sparse_major;
|
|
|
+ file->stat_info->sparse_minor = tar_sparse_minor;
|
|
|
+
|
|
|
+ if (file->stat_info->sparse_major == 0)
|
|
|
+ pax_dump_header_0 (file);
|
|
|
+ else
|
|
|
+ pax_dump_header_1 (file);
|
|
|
+}
|
|
|
+
|
|
|
+static bool
|
|
|
+decode_num (uintmax_t *num, char const *arg, uintmax_t maxval)
|
|
|
+{
|
|
|
+ uintmax_t u;
|
|
|
+ char *arg_lim;
|
|
|
+
|
|
|
+ if (!ISDIGIT (*arg))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ u = strtoumax (arg, &arg_lim, 10);
|
|
|
+
|
|
|
+ if (! (u <= maxval && errno != ERANGE) || *arg_lim)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ *num = u;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static bool
|
|
|
+pax_decode_header (struct tar_sparse_file *file)
|
|
|
+{
|
|
|
+ if (file->stat_info->sparse_major > 0)
|
|
|
+ {
|
|
|
+ uintmax_t u;
|
|
|
+ char nbuf[UINTMAX_STRSIZE_BOUND];
|
|
|
+ union block *blk;
|
|
|
+ char *p;
|
|
|
+ size_t i;
|
|
|
+
|
|
|
+#define COPY_BUF(b,buf,src) do \
|
|
|
+ { \
|
|
|
+ char *endp = b->buffer + BLOCKSIZE; \
|
|
|
+ char *dst = buf; \
|
|
|
+ do \
|
|
|
+ { \
|
|
|
+ if (dst == buf + UINTMAX_STRSIZE_BOUND -1) \
|
|
|
+ { \
|
|
|
+ ERROR ((0, 0, _("%s: numeric overflow in sparse archive member"), \
|
|
|
+ file->stat_info->orig_file_name)); \
|
|
|
+ return false; \
|
|
|
+ } \
|
|
|
+ if (src == endp) \
|
|
|
+ { \
|
|
|
+ set_next_block_after (b); \
|
|
|
+ b = find_next_block (); \
|
|
|
+ src = b->buffer; \
|
|
|
+ endp = b->buffer + BLOCKSIZE; \
|
|
|
+ } \
|
|
|
+ *dst = *src++; \
|
|
|
+ } \
|
|
|
+ while (*dst++ != '\n'); \
|
|
|
+ dst[-1] = 0; \
|
|
|
+ } while (0)
|
|
|
+
|
|
|
+ set_next_block_after (current_header);
|
|
|
+ blk = find_next_block ();
|
|
|
+ p = blk->buffer;
|
|
|
+ COPY_BUF (blk,nbuf,p);
|
|
|
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
|
|
|
+ {
|
|
|
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
|
|
|
+ file->stat_info->orig_file_name));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ file->stat_info->sparse_map_size = u;
|
|
|
+ file->stat_info->sparse_map = xcalloc (file->stat_info->sparse_map_size,
|
|
|
+ sizeof (*file->stat_info->sparse_map));
|
|
|
+ file->stat_info->sparse_map_avail = 0;
|
|
|
+ for (i = 0; i < file->stat_info->sparse_map_size; i++)
|
|
|
+ {
|
|
|
+ struct sp_array sp;
|
|
|
+
|
|
|
+ COPY_BUF (blk,nbuf,p);
|
|
|
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
|
|
|
+ {
|
|
|
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
|
|
|
+ file->stat_info->orig_file_name));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ sp.offset = u;
|
|
|
+ COPY_BUF (blk,nbuf,p);
|
|
|
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
|
|
|
+ {
|
|
|
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
|
|
|
+ file->stat_info->orig_file_name));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ sp.numbytes = u;
|
|
|
+ sparse_add_map (file->stat_info, &sp);
|
|
|
+ }
|
|
|
+ set_next_block_after (blk);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
static struct tar_sparse_optab const pax_optab = {
|
|
|
NULL, /* No init function */
|
|
|
NULL, /* No done function */
|
|
|
pax_sparse_member_p,
|
|
|
pax_dump_header,
|
|
|
- NULL, /* No decode_header function */
|
|
|
- NULL, /* No fixup_header function */
|
|
|
+ NULL,
|
|
|
+ pax_decode_header,
|
|
|
NULL, /* No scan_block function */
|
|
|
sparse_dump_region,
|
|
|
sparse_extract_region,
|