summaryrefslogtreecommitdiffstats
path: root/src/vfs/tar/tar-xheader.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vfs/tar/tar-xheader.c')
-rw-r--r--src/vfs/tar/tar-xheader.c1051
1 files changed, 1051 insertions, 0 deletions
diff --git a/src/vfs/tar/tar-xheader.c b/src/vfs/tar/tar-xheader.c
new file mode 100644
index 0000000..5062ed1
--- /dev/null
+++ b/src/vfs/tar/tar-xheader.c
@@ -0,0 +1,1051 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 1995-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
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+
+#include "tar-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define XHDR_PROTECTED 0x01
+#define XHDR_GLOBAL 0x02
+
+/*** file scope type declarations ****************************************************************/
+
+/* General Interface */
+
+/* Since tar VFS is read-only, inplement decodes only */
+/* *INDENT-OFF* */
+struct xhdr_tab
+{
+ const char *keyword;
+ gboolean (*decoder) (struct tar_stat_info * st, const char *keyword, const char *arg, size_t size);
+ int flags;
+};
+/* *INDENT-ON* */
+
+/* Keyword options */
+struct keyword_item
+{
+ char *pattern;
+ char *value;
+};
+
+enum decode_record_status
+{
+ decode_record_ok,
+ decode_record_finish,
+ decode_record_fail
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean dummy_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean atime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean gid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#if 0
+static gboolean gname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#endif
+static gboolean linkpath_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean mtime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean ctime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean uid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#if 0
+static gboolean uname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#endif
+static gboolean sparse_path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean sparse_major_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_minor_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean sparse_numblocks_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_offset_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_numbytes_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_map_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean dumpdir_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+
+/*** file scope variables ************************************************************************/
+
+enum
+{
+ BILLION = 1000000000,
+ LOG10_BILLION = 9
+};
+
+/* *INDENT-OFF* */
+static struct xhdr_tab xhdr_tab[] =
+{
+ { "atime", atime_decoder, 0 },
+ { "comment", dummy_decoder, 0 },
+ { "charset", dummy_decoder, 0 },
+ { "ctime", ctime_decoder, 0 },
+ { "gid", gid_decoder, 0 },
+#if 0
+ { "gname", gname_decoder, 0 },
+#endif
+ { "linkpath", linkpath_decoder, 0 },
+ { "mtime", mtime_decoder, 0 },
+ { "path", path_decoder, 0 },
+ { "size", size_decoder, 0 },
+ { "uid", uid_decoder, 0 },
+#if 0
+ { "uname", uname_decoder, 0 },
+#endif
+
+ /* Sparse file handling */
+ { "GNU.sparse.name", sparse_path_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.major", sparse_major_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.minor", sparse_minor_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.realsize", sparse_size_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.numblocks", sparse_numblocks_decoder, XHDR_PROTECTED },
+
+ { "GNU.sparse.size", sparse_size_decoder, XHDR_PROTECTED },
+ /* tar 1.14 - 1.15.1 keywords. Multiple instances of these appeared in 'x'
+ headers, and each of them was meaningful. It confilcted with POSIX specs,
+ which requires that "when extended header records conflict, the last one
+ given in the header shall take precedence." */
+ { "GNU.sparse.offset", sparse_offset_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.numbytes", sparse_numbytes_decoder, XHDR_PROTECTED },
+ /* tar 1.15.90 keyword, introduced to remove the above-mentioned conflict. */
+ { "GNU.sparse.map", sparse_map_decoder, 0 },
+
+ { "GNU.dumpdir", dumpdir_decoder, XHDR_PROTECTED },
+
+ { NULL, NULL, 0 }
+};
+/* *INDENT-ON* */
+
+/* List of keyword/value pairs decoded from the last 'g' type header */
+static GSList *global_header_override_list = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Convert a prefix of the string @arg to a system integer type whose minimum value is @minval
+ and maximum @maxval. If @minval is negative, negative integers @minval .. -1 are assumed
+ to be represented using leading '-' in the usual way. If the represented value exceeds INTMAX_MAX,
+ return a negative integer V such that (uintmax_t) V yields the represented value. If @arglim is
+ nonnull, store into *@arglim a pointer to the first character after the prefix.
+
+ This is the inverse of sysinttostr.
+
+ On a normal return, set errno = 0.
+ On conversion error, return 0 and set errno = EINVAL.
+ On overflow, return an extreme value and set errno = ERANGE.
+ */
+#if ! (INTMAX_MAX <= UINTMAX_MAX)
+#error "strtosysint: nonnegative intmax_t does not fit in uintmax_t"
+#endif
+static intmax_t
+strtosysint (const char *arg, char **arglim, intmax_t minval, uintmax_t maxval)
+{
+ errno = 0;
+
+ if (maxval <= INTMAX_MAX)
+ {
+ if (isdigit (arg[*arg == '-' ? 1 : 0]))
+ {
+ gint64 i;
+
+ i = g_ascii_strtoll (arg, arglim, 10);
+ if ((gint64) minval <= i && i <= (gint64) maxval)
+ return (intmax_t) i;
+
+ errno = ERANGE;
+ return i < (gint64) minval ? minval : (intmax_t) maxval;
+ }
+ }
+ else
+ {
+ if (isdigit (*arg))
+ {
+ guint64 i;
+
+ i = g_ascii_strtoull (arg, arglim, 10);
+ if (i <= (guint64) maxval)
+ return tar_represent_uintmax ((uintmax_t) i);
+
+ errno = ERANGE;
+ return maxval;
+ }
+ }
+
+ errno = EINVAL;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct xhdr_tab *
+locate_handler (const char *keyword)
+{
+ struct xhdr_tab *p;
+
+ for (p = xhdr_tab; p->keyword != NULL; p++)
+ if (strcmp (p->keyword, keyword) == 0)
+ return p;
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+keyword_item_run (gpointer data, gpointer user_data)
+{
+ struct keyword_item *kp = (struct keyword_item *) data;
+ struct tar_stat_info *st = (struct tar_stat_info *) user_data;
+ struct xhdr_tab const *t;
+
+ t = locate_handler (kp->pattern);
+ if (t != NULL)
+ return t->decoder (st, t->keyword, kp->value, strlen (kp->value));
+
+ return TRUE; /* FIXME */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+keyword_item_free (gpointer data)
+{
+ struct keyword_item *kp = (struct keyword_item *) data;
+
+ g_free (kp->pattern);
+ g_free (kp->value);
+ g_free (kp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+xheader_list_append (GSList ** root, const char *kw, const char *value)
+{
+ struct keyword_item *kp;
+
+ kp = g_new (struct keyword_item, 1);
+ kp->pattern = g_strdup (kw);
+ kp->value = g_strdup (value);
+ *root = g_slist_prepend (*root, kp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+xheader_list_destroy (GSList ** root)
+{
+ g_slist_free_full (*root, keyword_item_free);
+ *root = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+run_override_list (GSList * kp, struct tar_stat_info *st)
+{
+ g_slist_foreach (kp, (GFunc) keyword_item_run, st);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct timespec
+decode_timespec (const char *arg, char **arg_lim, gboolean parse_fraction)
+{
+ time_t s = TYPE_MINIMUM (time_t);
+ int ns = -1;
+ const char *p = arg;
+ gboolean negative = *arg == '-';
+ struct timespec r;
+
+ if (!isdigit (arg[negative]))
+ errno = EINVAL;
+ else
+ {
+ errno = 0;
+
+ if (negative)
+ {
+ gint64 i;
+
+ i = g_ascii_strtoll (arg, arg_lim, 10);
+ if (TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= i : 0 <= i)
+ s = (intmax_t) i;
+ else
+ errno = ERANGE;
+ }
+ else
+ {
+ guint64 i;
+
+ i = g_ascii_strtoull (arg, arg_lim, 10);
+ if (i <= TYPE_MAXIMUM (time_t))
+ s = (uintmax_t) i;
+ else
+ errno = ERANGE;
+ }
+
+ p = *arg_lim;
+ ns = 0;
+
+ if (parse_fraction && *p == '.')
+ {
+ int digits = 0;
+ gboolean trailing_nonzero = FALSE;
+
+ while (isdigit (*++p))
+ if (digits < LOG10_BILLION)
+ {
+ digits++;
+ ns = 10 * ns + (*p - '0');
+ }
+ else if (*p != '0')
+ trailing_nonzero = TRUE;
+
+ while (digits < LOG10_BILLION)
+ {
+ digits++;
+ ns *= 10;
+ }
+
+ if (negative)
+ {
+ /* Convert "-1.10000000000001" to s == -2, ns == 89999999.
+ I.e., truncate time stamps towards minus infinity while
+ converting them to internal form. */
+ if (trailing_nonzero)
+ ns++;
+ if (ns != 0)
+ {
+ if (s == TYPE_MINIMUM (time_t))
+ ns = -1;
+ else
+ {
+ s--;
+ ns = BILLION - ns;
+ }
+ }
+ }
+ }
+
+ if (errno == ERANGE)
+ ns = -1;
+ }
+
+ *arg_lim = (char *) p;
+ r.tv_sec = s;
+ r.tv_nsec = ns;
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_time (struct timespec *ts, const char *arg, const char *keyword)
+{
+ char *arg_lim;
+ struct timespec t;
+
+ (void) keyword;
+
+ t = decode_timespec (arg, &arg_lim, TRUE);
+
+ if (t.tv_nsec < 0)
+ /* Malformed extended header */
+ return FALSE;
+
+ if (*arg_lim != '\0')
+ /* Malformed extended header */
+ return FALSE;
+
+ *ts = t;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_signed_num (intmax_t * num, const char *arg, intmax_t minval, uintmax_t maxval,
+ const char *keyword)
+{
+ char *arg_lim;
+ intmax_t u;
+
+ (void) keyword;
+
+ if (!isdigit (*arg))
+ return FALSE; /* malformed extended header */
+
+ u = strtosysint (arg, &arg_lim, minval, maxval);
+
+ if (errno == EINVAL || *arg_lim != '\0')
+ return FALSE; /* malformed extended header */
+
+ if (errno == ERANGE)
+ return FALSE; /* out of range */
+
+ *num = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_num (uintmax_t * num, const char *arg, uintmax_t maxval, const char *keyword)
+{
+ intmax_t i;
+
+ if (!decode_signed_num (&i, arg, 0, maxval, keyword))
+ return FALSE;
+
+ *num = i;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+raw_path_decoder (struct tar_stat_info *st, const char *arg)
+{
+ if (*arg != '\0')
+ {
+ tar_assign_string_dup (&st->orig_file_name, arg);
+ tar_assign_string_dup (&st->file_name, arg);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dummy_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) st;
+ (void) keyword;
+ (void) arg;
+ (void) size;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+atime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->atime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+gid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ intmax_t u;
+
+ (void) size;
+
+ if (!decode_signed_num (&u, arg, TYPE_MINIMUM (gid_t), TYPE_MINIMUM (gid_t), keyword))
+ return FALSE;
+
+ st->stat.st_gid = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+gname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->gname, arg);
+ return TRUE;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+linkpath_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->link_name, arg);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ctime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->ctime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mtime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->mtime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ if (!st->sparse_name_done)
+ return raw_path_decoder (st, arg);
+
+ return TRUE; /* FIXME */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ st->stat.st_size = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+uid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ intmax_t u;
+
+ (void) size;
+
+ if (!decode_signed_num (&u, arg, TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), keyword))
+ return FALSE;
+
+ st->stat.st_uid = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+uname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->uname, arg);
+ return TRUE;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dumpdir_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+
+#if GLIB_CHECK_VERSION (2, 68, 0)
+ st->dumpdir = g_memdup2 (arg, size);
+#else
+ st->dumpdir = g_memdup (arg, size);
+#endif
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Decodes a single extended header record, advancing @ptr to the next record.
+ *
+ * @param p pointer to extended header record
+ * @param st stat info
+ *
+ * @return decode_record_ok or decode_record_finish on success, decode_record_fail otherwize
+ */
+static enum decode_record_status
+decode_record (struct xheader *xhdr, char **ptr,
+ gboolean (*handler) (void *data, const char *keyword, const char *value,
+ size_t size), void *data)
+{
+ char *start = *ptr;
+ char *p = start;
+ size_t len;
+ char *len_lim;
+ const char *keyword;
+ char *nextp;
+ size_t len_max;
+ gboolean ret;
+
+ len_max = xhdr->buffer + xhdr->size - start;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (!isdigit (*p))
+ return (*p != '\0' ? decode_record_fail : decode_record_finish);
+
+ len = (uintmax_t) g_ascii_strtoull (p, &len_lim, 10);
+ if (len_max < len)
+ return decode_record_fail;
+
+ nextp = start + len;
+
+ for (p = len_lim; *p == ' ' || *p == '\t'; p++)
+ ;
+
+ if (p == len_lim)
+ return decode_record_fail;
+
+ keyword = p;
+ p = strchr (p, '=');
+ if (!(p != NULL && p < nextp))
+ return decode_record_fail;
+
+ if (nextp[-1] != '\n')
+ return decode_record_fail;
+
+ *p = nextp[-1] = '\0';
+ ret = handler (data, keyword, p + 1, nextp - p - 2); /* '=' + trailing '\n' */
+ *p = '=';
+ nextp[-1] = '\n';
+ *ptr = nextp;
+
+ return (ret ? decode_record_ok : decode_record_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decg (void *data, const char *keyword, const char *value, size_t size)
+{
+ GSList **kwl = (GSList **) data;
+ struct xhdr_tab const *tab;
+
+ (void) size;
+
+ tab = locate_handler (keyword);
+ if (tab != NULL && (tab->flags & XHDR_GLOBAL) != 0)
+ {
+ if (!tab->decoder (data, keyword, value, size))
+ return FALSE;
+ }
+ else
+ xheader_list_append (kwl, keyword, value);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decx (void *data, const char *keyword, const char *value, size_t size)
+{
+ struct keyword_item kp = {
+ .pattern = (char *) keyword,
+ .value = (char *) value
+ };
+
+ (void) size;
+
+ return keyword_item_run (&kp, data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ st->sparse_name_done = TRUE;
+ return raw_path_decoder (st, arg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_major_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ return FALSE;
+
+ st->sparse_major = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_minor_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ return FALSE;
+
+ st->sparse_minor = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ st->real_size_set = TRUE;
+ st->real_size = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_numblocks_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, SIZE_MAX, keyword))
+ return FALSE;
+
+ if (st->sparse_map == NULL)
+ st->sparse_map = g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), u);
+ else
+ g_array_set_size (st->sparse_map, u);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_offset_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+ struct sp_array *s;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ s = &g_array_index (st->sparse_map, struct sp_array, st->sparse_map->len - 1);
+ s->offset = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_numbytes_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size)
+{
+ uintmax_t u;
+ struct sp_array s;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, SIZE_MAX, keyword))
+ return FALSE;
+
+ s.offset = 0;
+ s.numbytes = u;
+ g_array_append_val (st->sparse_map, s);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_map_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ gboolean offset = TRUE;
+ struct sp_array e;
+
+ (void) keyword;
+ (void) size;
+
+ if (st->sparse_map != NULL)
+ g_array_set_size (st->sparse_map, 0);
+
+ while (TRUE)
+ {
+ gint64 u;
+ char *delim;
+
+ if (!isdigit (*arg))
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ errno = 0;
+ u = g_ascii_strtoll (arg, &delim, 10);
+ if (TYPE_MAXIMUM (off_t) < u)
+ {
+ u = TYPE_MAXIMUM (off_t);
+ errno = ERANGE;
+ }
+ if (offset)
+ {
+ e.offset = u;
+ if (errno == ERANGE)
+ {
+ /* out of range */
+ return FALSE;
+ }
+ }
+ else
+ {
+ e.numbytes = u;
+ if (errno == ERANGE)
+ {
+ /* out of range */
+ return FALSE;
+ }
+
+ g_array_append_val (st->sparse_map, e);
+ }
+
+ offset = !offset;
+
+ if (*delim == '\0')
+ break;
+ if (*delim != ',')
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ arg = delim + 1;
+ }
+
+ if (!offset)
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Decodes an extended header.
+ *
+ * @param st stat info
+ *
+ * @return TRUE on success, FALSE otherwize
+ */
+gboolean
+tar_xheader_decode (struct tar_stat_info * st)
+{
+ char *p;
+ enum decode_record_status status;
+
+ run_override_list (global_header_override_list, st);
+
+ p = st->xhdr.buffer + BLOCKSIZE;
+
+ while ((status = decode_record (&st->xhdr, &p, decx, st)) == decode_record_ok)
+ ;
+
+ if (status == decode_record_fail)
+ return FALSE;
+
+ /* The archived (effective) file size is always set directly in tar header
+ field, possibly overridden by "size" extended header - in both cases,
+ result is now decoded in st->stat.st_size */
+ st->archive_file_size = st->stat.st_size;
+
+ /* The real file size (given by stat()) may be redefined for sparse
+ files in "GNU.sparse.realsize" extended header */
+ if (st->real_size_set)
+ st->stat.st_size = st->real_size;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_xheader_read (tar_super_t * archive, struct xheader * xhdr, union block * p, off_t size)
+{
+ size_t j = 0;
+
+ size = MAX (0, size);
+ size += BLOCKSIZE;
+
+ xhdr->size = size;
+ xhdr->buffer = g_malloc (size + 1);
+ xhdr->buffer[size] = '\0';
+
+ do
+ {
+ size_t len;
+
+ if (p == NULL)
+ return FALSE; /* Unexpected EOF in archive */
+
+ len = MIN (size, BLOCKSIZE);
+
+ memcpy (xhdr->buffer + j, p->buffer, len);
+ tar_set_next_block_after (p);
+ p = tar_find_next_block (archive);
+
+ j += len;
+ size -= len;
+ }
+ while (size > 0);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_xheader_decode_global (struct xheader * xhdr)
+{
+ char *p;
+ gboolean ret;
+
+ p = xhdr->buffer + BLOCKSIZE;
+
+ xheader_list_destroy (&global_header_override_list);
+
+ while ((ret = decode_record (xhdr, &p, decg, &global_header_override_list)) == decode_record_ok)
+ ;
+
+ return (ret == decode_record_finish);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_xheader_destroy (struct xheader *xhdr)
+{
+ MC_PTR_FREE (xhdr->buffer);
+ xhdr->size = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */