/* Virtual File System: GNU Tar file system. Copyright (C) 1995-2023 Free Software Foundation, Inc. Written by: Andrew Borodin , 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 . */ /** * \file * \brief Source: Virtual File System: GNU Tar file system */ #include #include /* isdigit() */ #include #include #include #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; } /* --------------------------------------------------------------------------------------------- */