summaryrefslogtreecommitdiffstats
path: root/src/vfs/tar/tar-sparse.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vfs/tar/tar-sparse.c')
-rw-r--r--src/vfs/tar/tar-sparse.c777
1 files changed, 777 insertions, 0 deletions
diff --git a/src/vfs/tar/tar-sparse.c b/src/vfs/tar/tar-sparse.c
new file mode 100644
index 0000000..0bc169b
--- /dev/null
+++ b/src/vfs/tar/tar-sparse.c
@@ -0,0 +1,777 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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.
+
+ The Midnight Commander 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/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ */
+
+/*
+ * Avoid following error:
+ * comparison of unsigned expression < 0 is always false [-Werror=type-limits]
+ *
+ * https://www.boost.org/doc/libs/1_55_0/libs/integer/test/cstdint_test.cpp
+ * We can't suppress this warning on the command line as not all GCC versions support -Wno-type-limits
+ */
+#if defined(__GNUC__) && (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4))
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+
+#include "tar-internal.h"
+
+/* Old GNU Format.
+ The sparse file information is stored in the oldgnu_header in the following manner:
+
+ The header is marked with type 'S'. Its 'size' field contains the cumulative size
+ of all non-empty blocks of the file. The actual file size is stored in `realsize'
+ member of oldgnu_header.
+
+ The map of the file is stored in a list of 'struct sparse'. Each struct contains
+ offset to the block of data and its size (both as octal numbers). The first file
+ header contains at most 4 such structs (SPARSES_IN_OLDGNU_HEADER). If the map
+ contains more structs, then the field 'isextended' of the main header is set to
+ 1 (binary) and the 'struct sparse_header' header follows, containing at most
+ 21 following structs (SPARSES_IN_SPARSE_HEADER). If more structs follow, 'isextended'
+ field of the extended header is set and next next extension header follows, etc...
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* The width in bits of the integer type or expression T.
+ Do not evaluate T. T must not be a bit-field expression.
+ Padding bits are not supported; this is checked at compile-time below. */
+#define TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT)
+
+/* Bound on length of the string representing an unsigned integer
+ value representable in B bits. log10 (2.0) < 146/485. The
+ smallest value of B where this bound is not tight is 2621. */
+#define INT_BITS_STRLEN_BOUND(b) (((b) * 146 + 484) / 485)
+
+/* Does the __typeof__ keyword work? This could be done by
+ 'configure', but for now it's easier to do it by hand. */
+#if (2 <= __GNUC__ \
+ || (4 <= __clang_major__) \
+ || (1210 <= __IBMC__ && defined __IBM__TYPEOF__) \
+ || (0x5110 <= __SUNPRO_C && !__STDC__))
+#define _GL_HAVE___TYPEOF__ 1
+#else
+#define _GL_HAVE___TYPEOF__ 0
+#endif
+
+/* Return 1 if the integer type or expression T might be signed. Return 0
+ if it is definitely unsigned. T must not be a bit-field expression.
+ This macro does not evaluate its argument, and expands to an
+ integer constant expression. */
+#if _GL_HAVE___TYPEOF__
+#define _GL_SIGNED_TYPE_OR_EXPR(t) TYPE_SIGNED (__typeof__ (t))
+#else
+#define _GL_SIGNED_TYPE_OR_EXPR(t) 1
+#endif
+
+/* Return a value with the common real type of E and V and the value of V.
+ Do not evaluate E. */
+#define _GL_INT_CONVERT(e, v) ((1 ? 0 : (e)) + (v))
+
+/* Act like _GL_INT_CONVERT (E, -V) but work around a bug in IRIX 6.5 cc; see
+ <https://lists.gnu.org/r/bug-gnulib/2011-05/msg00406.html>. */
+#define _GL_INT_NEGATE_CONVERT(e, v) ((1 ? 0 : (e)) - (v))
+
+/* Return 1 if the real expression E, after promotion, has a
+ signed or floating type. Do not evaluate E. */
+#define EXPR_SIGNED(e) (_GL_INT_NEGATE_CONVERT (e, 1) < 0)
+
+#define _GL_SIGNED_INT_MAXIMUM(e) \
+ (((_GL_INT_CONVERT (e, 1) << (TYPE_WIDTH (+ (e)) - 2)) - 1) * 2 + 1)
+
+/* The maximum and minimum values for the type of the expression E,
+ after integer promotion. E is not evaluated. */
+#define _GL_INT_MINIMUM(e) \
+ (EXPR_SIGNED (e) \
+ ? ~_GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_CONVERT (e, 0))
+#define _GL_INT_MAXIMUM(e) \
+ (EXPR_SIGNED (e) \
+ ? _GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_NEGATE_CONVERT (e, 1))
+
+/* Return 1 if the expression A <op> B would overflow,
+ where OP_RESULT_OVERFLOW (A, B, MIN, MAX) does the actual test,
+ assuming MIN and MAX are the minimum and maximum for the result type.
+ Arguments should be free of side effects. */
+#define _GL_BINARY_OP_OVERFLOW(a, b, op_result_overflow) \
+ op_result_overflow (a, b, \
+ _GL_INT_MINIMUM (_GL_INT_CONVERT (a, b)), \
+ _GL_INT_MAXIMUM (_GL_INT_CONVERT (a, b)))
+
+#define INT_ADD_RANGE_OVERFLOW(a, b, min, max) \
+ ((b) < 0 \
+ ? (a) < (min) - (b) \
+ : (max) - (b) < (a))
+
+
+/* True if __builtin_add_overflow_p (A, B, C) works, and similarly for
+ __builtin_sub_overflow_p and __builtin_mul_overflow_p. */
+#if defined __clang__ || defined __ICC
+/* Clang 11 lacks __builtin_mul_overflow_p, and even if it did it
+ would presumably run afoul of Clang bug 16404. ICC 2021.1's
+ __builtin_add_overflow_p etc. are not treated as integral constant
+ expressions even when all arguments are. */
+#define _GL_HAS_BUILTIN_OVERFLOW_P 0
+#elif defined __has_builtin
+#define _GL_HAS_BUILTIN_OVERFLOW_P __has_builtin (__builtin_mul_overflow_p)
+#else
+#define _GL_HAS_BUILTIN_OVERFLOW_P (7 <= __GNUC__)
+#endif
+
+/* The _GL*_OVERFLOW macros have the same restrictions as the
+ *_RANGE_OVERFLOW macros, except that they do not assume that operands
+ (e.g., A and B) have the same type as MIN and MAX. Instead, they assume
+ that the result (e.g., A + B) has that type. */
+#if _GL_HAS_BUILTIN_OVERFLOW_P
+#define _GL_ADD_OVERFLOW(a, b, min, max) \
+ __builtin_add_overflow_p (a, b, (__typeof__ ((a) + (b))) 0)
+#else
+#define _GL_ADD_OVERFLOW(a, b, min, max) \
+ ((min) < 0 ? INT_ADD_RANGE_OVERFLOW (a, b, min, max) \
+ : (a) < 0 ? (b) <= (a) + (b) \
+ : (b) < 0 ? (a) <= (a) + (b) \
+ : (a) + (b) < (b))
+#endif
+
+/* Bound on length of the string representing an integer type or expression T.
+ T must not be a bit-field expression.
+
+ Subtract 1 for the sign bit if T is signed, and then add 1 more for
+ a minus sign if needed.
+
+ Because _GL_SIGNED_TYPE_OR_EXPR sometimes returns 1 when its argument is
+ unsigned, this macro may overestimate the true bound by one byte when
+ applied to unsigned types of size 2, 4, 16, ... bytes. */
+#define INT_STRLEN_BOUND(t) \
+ (INT_BITS_STRLEN_BOUND (TYPE_WIDTH (t) - _GL_SIGNED_TYPE_OR_EXPR (t)) \
+ + _GL_SIGNED_TYPE_OR_EXPR (t))
+
+/* Bound on buffer size needed to represent an integer type or expression T,
+ including the terminating null. T must not be a bit-field expression. */
+#define INT_BUFSIZE_BOUND(t) (INT_STRLEN_BOUND (t) + 1)
+
+#define UINTMAX_STRSIZE_BOUND INT_BUFSIZE_BOUND (uintmax_t)
+
+#define INT_ADD_OVERFLOW(a, b) \
+ _GL_BINARY_OP_OVERFLOW (a, b, _GL_ADD_OVERFLOW)
+
+#define SPARSES_INIT_COUNT SPARSES_IN_SPARSE_HEADER
+
+#define COPY_BUF(arch,b,buf,src) \
+do \
+{ \
+ char *endp = b->buffer + BLOCKSIZE; \
+ char *dst = buf; \
+ do \
+ { \
+ if (dst == buf + UINTMAX_STRSIZE_BOUND - 1) \
+ /* numeric overflow in sparse archive member */ \
+ return FALSE; \
+ if (src == endp) \
+ { \
+ tar_set_next_block_after (b); \
+ b = tar_find_next_block (arch); \
+ if (b == NULL) \
+ /* unexpected EOF in archive */ \
+ return FALSE; \
+ src = b->buffer; \
+ endp = b->buffer + BLOCKSIZE; \
+ } \
+ *dst = *src++; \
+ } \
+ while (*dst++ != '\n'); \
+ dst[-1] = '\0'; \
+} \
+while (FALSE)
+
+/*** file scope type declarations ****************************************************************/
+
+struct tar_sparse_file;
+
+struct tar_sparse_optab
+{
+ gboolean (*init) (struct tar_sparse_file * file);
+ gboolean (*done) (struct tar_sparse_file * file);
+ gboolean (*sparse_member_p) (struct tar_sparse_file * file);
+ gboolean (*fixup_header) (struct tar_sparse_file * file);
+ gboolean (*decode_header) (tar_super_t * archive, struct tar_sparse_file * file);
+};
+
+struct tar_sparse_file
+{
+ int fd; /**< File descriptor */
+ off_t dumped_size; /**< Number of bytes actually written to the archive */
+ struct tar_stat_info *stat_info; /**< Information about the file */
+ struct tar_sparse_optab const *optab;
+ void *closure; /**< Any additional data optab calls might reqiure */
+};
+
+enum oldgnu_add_status
+{
+ add_ok,
+ add_finish,
+ add_fail
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean oldgnu_sparse_member_p (struct tar_sparse_file *file);
+static gboolean oldgnu_fixup_header (struct tar_sparse_file *file);
+static gboolean oldgnu_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file);
+
+static gboolean star_sparse_member_p (struct tar_sparse_file *file);
+static gboolean star_fixup_header (struct tar_sparse_file *file);
+static gboolean star_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file);
+
+static gboolean pax_sparse_member_p (struct tar_sparse_file *file);
+static gboolean pax_decode_header (tar_super_t * archive, struct tar_sparse_file *file);
+
+/*** file scope variables ************************************************************************/
+
+/* *INDENT-OFF* */
+static struct tar_sparse_optab const oldgnu_optab =
+{
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = oldgnu_sparse_member_p,
+ .fixup_header = oldgnu_fixup_header,
+ .decode_header = oldgnu_get_sparse_info
+};
+/* *INDENT-ON* */
+
+/* *INDENT-OFF* */
+static struct tar_sparse_optab const star_optab =
+{
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = star_sparse_member_p,
+ .fixup_header = star_fixup_header,
+ .decode_header = star_get_sparse_info
+};
+/* *INDENT-ON* */
+
+/* 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 repeat numblocks time
+ GNU.sparse.offset Offset of the next data block
+ GNU.sparse.numbytes Size of the next data block end repeat
+
+ 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, whereas POSIX requires the
+ latest occurrence of the variable to override all previous occurrences.
+
+ 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
+
+ Starting from this version, the exact sparse format version is specified explicitly
+ 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 struct tar_sparse_optab const pax_optab = {
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = pax_sparse_member_p,
+ .fixup_header = NULL, /* No fixup_header function */
+ .decode_header = pax_decode_header
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_num (uintmax_t * num, const char *arg, uintmax_t maxval)
+{
+ uintmax_t u;
+ char *arg_lim;
+
+ if (!isdigit (*arg))
+ return FALSE;
+
+ errno = 0;
+ u = (uintmax_t) g_ascii_strtoll (arg, &arg_lim, 10);
+
+ if (!(u <= maxval && errno != ERANGE) || *arg_lim != '\0')
+ return FALSE;
+
+ *num = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_select_optab (const tar_super_t * archive, struct tar_sparse_file *file)
+{
+ switch (archive->type)
+ {
+ case TAR_V7:
+ case TAR_USTAR:
+ return FALSE;
+
+ case TAR_OLDGNU:
+ case TAR_GNU: /* FIXME: This one should disappear? */
+ file->optab = &oldgnu_optab;
+ break;
+
+ case TAR_POSIX:
+ file->optab = &pax_optab;
+ break;
+
+ case TAR_STAR:
+ file->optab = &star_optab;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_init (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ memset (file, 0, sizeof (*file));
+
+ if (!sparse_select_optab (archive, file))
+ return FALSE;
+
+ if (file->optab->init != NULL)
+ return file->optab->init (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_done (struct tar_sparse_file *file)
+{
+ if (file->optab->done != NULL)
+ return file->optab->done (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_member_p (struct tar_sparse_file *file)
+{
+ if (file->optab->sparse_member_p != NULL)
+ return file->optab->sparse_member_p (file);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_fixup_header (struct tar_sparse_file *file)
+{
+ if (file->optab->fixup_header != NULL)
+ return file->optab->fixup_header (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_decode_header (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ if (file->optab->decode_header != NULL)
+ return file->optab->decode_header (archive, file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+sparse_add_map (struct tar_stat_info *st, struct sp_array *sp)
+{
+ if (st->sparse_map == NULL)
+ st->sparse_map = g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), 1);
+ g_array_append_val (st->sparse_map, *sp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Add a sparse item to the sparse file
+ */
+static enum oldgnu_add_status
+oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
+{
+ struct sp_array sp;
+
+ if (s->numbytes[0] == '\0')
+ return add_finish;
+
+ sp.offset = OFF_FROM_HEADER (s->offset);
+ sp.numbytes = OFF_FROM_HEADER (s->numbytes);
+
+ if (sp.offset < 0 || sp.numbytes < 0
+ || INT_ADD_OVERFLOW (sp.offset, sp.numbytes)
+ || file->stat_info->stat.st_size < sp.offset + sp.numbytes
+ || file->stat_info->archive_file_size < 0)
+ return add_fail;
+
+ sparse_add_map (file->stat_info, &sp);
+
+ return add_ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+oldgnu_sparse_member_p (struct tar_sparse_file *file)
+{
+ (void) file;
+
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+oldgnu_fixup_header (struct tar_sparse_file *file)
+{
+ /* NOTE! st_size was initialized from the header which actually contains archived size.
+ The following fixes it */
+ off_t realsize;
+
+ realsize = OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size = MAX (0, realsize);
+
+ return (realsize >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert old GNU format sparse data to internal representation.
+ */
+static gboolean
+oldgnu_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p;
+ enum oldgnu_add_status rc;
+
+ if (file->stat_info->sparse_map != NULL)
+ g_array_set_size (file->stat_info->sparse_map, 0);
+
+ for (i = 0; i < SPARSES_IN_OLDGNU_HEADER; i++)
+ {
+ rc = oldgnu_add_sparse (file, &h->oldgnu_header.sp[i]);
+ if (rc != add_ok)
+ break;
+ }
+
+ for (ext_p = h->oldgnu_header.isextended ? 1 : 0; rc == add_ok && ext_p != 0;
+ ext_p = h->sparse_header.isextended ? 1 : 0)
+ {
+ h = tar_find_next_block (archive);
+ if (h == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (h);
+
+ for (i = 0; i < SPARSES_IN_SPARSE_HEADER && rc == add_ok; i++)
+ rc = oldgnu_add_sparse (file, &h->sparse_header.sp[i]);
+ }
+
+ return (rc != add_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+star_sparse_member_p (struct tar_sparse_file *file)
+{
+ (void) file;
+
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+star_fixup_header (struct tar_sparse_file *file)
+{
+ /* NOTE! st_size was initialized from the header which actually contains archived size.
+ The following fixes it */
+ off_t realsize;
+
+ realsize = OFF_FROM_HEADER (current_header->star_in_header.realsize);
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size = MAX (0, realsize);
+
+ return (realsize >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert STAR format sparse data to internal representation
+ */
+static gboolean
+star_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p = 1;
+ enum oldgnu_add_status rc = add_ok;
+
+ if (file->stat_info->sparse_map != NULL)
+ g_array_set_size (file->stat_info->sparse_map, 0);
+
+ if (h->star_in_header.prefix[0] == '\0' && h->star_in_header.sp[0].offset[10] != '\0')
+ {
+ /* Old star format */
+ for (i = 0; i < SPARSES_IN_STAR_HEADER; i++)
+ {
+ rc = oldgnu_add_sparse (file, &h->star_in_header.sp[i]);
+ if (rc != add_ok)
+ break;
+ }
+
+ ext_p = h->star_in_header.isextended ? 1 : 0;
+ }
+
+ for (; rc == add_ok && ext_p != 0; ext_p = h->star_ext_header.isextended ? 1 : 0)
+ {
+ h = tar_find_next_block (archive);
+ if (h == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (h);
+
+ for (i = 0; i < SPARSES_IN_STAR_EXT_HEADER && rc == add_ok; i++)
+ rc = oldgnu_add_sparse (file, &h->star_ext_header.sp[i]);
+
+ file->dumped_size += BLOCKSIZE;
+ }
+
+ return (rc != add_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+pax_sparse_member_p (struct tar_sparse_file *file)
+{
+ return file->stat_info->sparse_map != NULL && file->stat_info->sparse_map->len > 0
+ && file->stat_info->sparse_major > 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+pax_decode_header (tar_super_t * archive, 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 sparse_map_len;
+ size_t i;
+ off_t start;
+
+ start = tar_current_block_ordinal (archive);
+ tar_set_next_block_after (current_header);
+ blk = tar_find_next_block (archive);
+ if (blk == NULL)
+ /* unexpected EOF in archive */
+ return FALSE;
+ p = blk->buffer;
+ COPY_BUF (archive, blk, nbuf, p);
+
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+
+ if (file->stat_info->sparse_map == NULL)
+ file->stat_info->sparse_map =
+ g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), u);
+ else
+ g_array_set_size (file->stat_info->sparse_map, u);
+
+ sparse_map_len = u;
+
+ for (i = 0; i < sparse_map_len; i++)
+ {
+ struct sp_array sp;
+
+ COPY_BUF (archive, blk, nbuf, p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+ sp.offset = u;
+ COPY_BUF (archive, blk, nbuf, p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)) || INT_ADD_OVERFLOW (sp.offset, u)
+ || (uintmax_t) file->stat_info->stat.st_size < sp.offset + u)
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+ sp.numbytes = u;
+ sparse_add_map (file->stat_info, &sp);
+ }
+
+ tar_set_next_block_after (blk);
+
+ file->dumped_size += BLOCKSIZE * (tar_current_block_ordinal (archive) - start);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_sparse_member_p (tar_super_t * archive, struct tar_stat_info * st)
+{
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return FALSE;
+
+ file.stat_info = st;
+ return sparse_member_p (&file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_sparse_fixup_header (tar_super_t * archive, struct tar_stat_info * st)
+{
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return FALSE;
+
+ file.stat_info = st;
+ return sparse_fixup_header (&file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+enum dump_status
+tar_sparse_skip_file (tar_super_t * archive, struct tar_stat_info *st)
+{
+ gboolean rc = TRUE;
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return dump_status_not_implemented;
+
+ file.stat_info = st;
+ file.fd = -1;
+
+ rc = sparse_decode_header (archive, &file);
+ (void) tar_skip_file (archive, file.stat_info->archive_file_size - file.dumped_size);
+ return (sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
+}
+
+/* --------------------------------------------------------------------------------------------- */