summaryrefslogtreecommitdiffstats
path: root/src/vfs/tar/tar-internal.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vfs/tar/tar-internal.c')
-rw-r--r--src/vfs/tar/tar-internal.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/src/vfs/tar/tar-internal.c b/src/vfs/tar/tar-internal.c
new file mode 100644
index 0000000..f77b1b3
--- /dev/null
+++ b/src/vfs/tar/tar-internal.c
@@ -0,0 +1,482 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 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
+ * \author Andrew Borodin
+ * \date 2022
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isspace() */
+#include <inttypes.h> /* uintmax_t */
+#include <stdint.h> /* UINTMAX_MAX, etc */
+
+#include "lib/global.h"
+#include "lib/widget.h" /* message() */
+#include "lib/vfs/vfs.h" /* mc_read() */
+
+#include "tar-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* Log base 2 of common values. */
+#define LG_8 3
+#define LG_64 6
+#define LG_256 8
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Base 64 digits; see RFC 2045 Table 1. */
+static char const base_64_digits[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+/* Table of base 64 digit values indexed by unsigned chars.
+ The value is 64 for unsigned chars that are not base 64 digits. */
+static char base64_map[1 + (unsigned char) (-1)];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_short_read (size_t status, tar_super_t * archive)
+{
+ size_t left; /* bytes left */
+ char *more; /* pointer to next byte to read */
+
+ more = archive->record_start->buffer + status;
+ left = record_size - status;
+
+ while (left % BLOCKSIZE != 0 || (left != 0 && status != 0))
+ {
+ if (status != 0)
+ {
+ ssize_t r;
+
+ r = mc_read (archive->fd, more, left);
+ if (r == -1)
+ return FALSE;
+
+ status = (size_t) r;
+ }
+
+ if (status == 0)
+ break;
+
+ left -= status;
+ more += status;
+ }
+
+ record_end = archive->record_start + (record_size - left) / BLOCKSIZE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_flush_read (tar_super_t * archive)
+{
+ size_t status;
+
+ status = mc_read (archive->fd, archive->record_start->buffer, record_size);
+ if (status == record_size)
+ return TRUE;
+
+ return tar_short_read (status, archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Flush the current buffer from the archive.
+ */
+static gboolean
+tar_flush_archive (tar_super_t * archive)
+{
+ record_start_block += record_end - archive->record_start;
+ current_block = archive->record_start;
+ record_end = archive->record_start + blocking_factor;
+
+ return tar_flush_read (archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+tar_seek_archive (tar_super_t * archive, off_t size)
+{
+ off_t start, offset;
+ off_t nrec, nblk;
+ off_t skipped;
+
+ skipped = (blocking_factor - (current_block - archive->record_start)) * BLOCKSIZE;
+ if (size <= skipped)
+ return 0;
+
+ /* Compute number of records to skip */
+ nrec = (size - skipped) / record_size;
+ if (nrec == 0)
+ return 0;
+
+ start = tar_current_block_ordinal (archive);
+
+ offset = mc_lseek (archive->fd, nrec * record_size, SEEK_CUR);
+ if (offset < 0)
+ return offset;
+
+#if 0
+ if ((offset % record_size) != 0)
+ {
+ message (D_ERROR, MSG_ERROR, _("tar: mc_lseek not stopped at a record boundary"));
+ return -1;
+ }
+#endif
+
+ /* Convert to number of records */
+ offset /= BLOCKSIZE;
+ /* Compute number of skipped blocks */
+ nblk = offset - start;
+
+ /* Update buffering info */
+ record_start_block = offset - blocking_factor;
+ current_block = record_end;
+
+ return nblk;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_base64_init (void)
+{
+ size_t i;
+
+ memset (base64_map, 64, sizeof base64_map);
+ for (i = 0; i < 64; i++)
+ base64_map[(int) base_64_digits[i]] = i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string (char **string, char *value)
+{
+ g_free (*string);
+ *string = value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string_dup (char **string, const char *value)
+{
+ g_free (*string);
+ *string = g_strdup (value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string_dup_n (char **string, const char *value, size_t n)
+{
+ g_free (*string);
+ *string = g_strndup (value, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert buffer at @where0 of size @digs from external format to intmax_t.
+ * @digs must be positive.
+ * If @type is non-NULL, data are of type @type.
+ * The buffer must represent a value in the range -@minval through @maxval;
+ * if the mathematically correct result V would be greater than INTMAX_MAX,
+ * return a negative integer V such that (uintmax_t) V yields the correct result.
+ * If @octal_only, allow only octal numbers instead of the other GNU extensions.
+ *
+ * Result is -1 if the field is invalid.
+ */
+#if !(INTMAX_MAX <= UINTMAX_MAX && - (INTMAX_MIN + 1) <= UINTMAX_MAX)
+#error "tar_from_header() internally represents intmax_t as uintmax_t + sign"
+#endif
+#if !(UINTMAX_MAX / 2 <= INTMAX_MAX)
+#error "tar_from_header() returns intmax_t to represent uintmax_t"
+#endif
+intmax_t
+tar_from_header (const char *where0, size_t digs, char const *type, intmax_t minval,
+ uintmax_t maxval, gboolean octal_only)
+{
+ uintmax_t value = 0;
+ uintmax_t uminval = minval;
+ uintmax_t minus_minval = -uminval;
+ const char *where = where0;
+ char const *lim = where + digs;
+ gboolean negative = FALSE;
+
+ /* Accommodate buggy tar of unknown vintage, which outputs leading
+ NUL if the previous field overflows. */
+ if (*where == '\0')
+ where++;
+
+ /* Accommodate older tars, which output leading spaces. */
+ while (TRUE)
+ {
+ if (where == lim)
+ return (-1);
+
+ if (!isspace ((unsigned char) *where))
+ break;
+
+ where++;
+ }
+
+ if (isodigit (*where))
+ {
+ char const *where1 = where;
+ gboolean overflow = FALSE;
+
+ while (TRUE)
+ {
+ value += *where++ - '0';
+ if (where == lim || !isodigit (*where))
+ break;
+ overflow |= value != (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ /* Parse the output of older, unportable tars, which generate
+ negative values in two's complement octal. If the leading
+ nonzero digit is 1, we can't recover the original value
+ reliably; so do this only if the digit is 2 or more. This
+ catches the common case of 32-bit negative time stamps. */
+ if ((overflow || maxval < value) && *where1 >= 2 && type != NULL)
+ {
+ /* Compute the negative of the input value, assuming two's complement. */
+ int digit;
+
+ digit = (*where1 - '0') | 4;
+ overflow = FALSE;
+ value = 0;
+ where = where1;
+
+ while (TRUE)
+ {
+ value += 7 - digit;
+ where++;
+ if (where == lim || !isodigit (*where))
+ break;
+ digit = *where - '0';
+ overflow |= value != (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ value++;
+ overflow |= value == 0;
+
+ if (!overflow && value <= minus_minval)
+ negative = TRUE;
+ }
+
+ if (overflow)
+ return (-1);
+ }
+ else if (octal_only)
+ {
+ /* Suppress the following extensions. */
+ }
+ else if (*where == '-' || *where == '+')
+ {
+ /* Parse base-64 output produced only by tar test versions
+ 1.13.6 (1999-08-11) through 1.13.11 (1999-08-23).
+ Support for this will be withdrawn in future tar releases. */
+ int dig;
+
+ negative = *where++ == '-';
+
+ while (where != lim && (dig = base64_map[(unsigned char) *where]) < 64)
+ {
+ if (value << LG_64 >> LG_64 != value)
+ return (-1);
+ value = (value << LG_64) | dig;
+ where++;
+ }
+ }
+ else if (where <= lim - 2 && (*where == '\200' /* positive base-256 */
+ || *where == '\377' /* negative base-256 */ ))
+ {
+ /* Parse base-256 output. A nonnegative number N is
+ represented as (256**DIGS)/2 + N; a negative number -N is
+ represented as (256**DIGS) - N, i.e. as two's complement.
+ The representation guarantees that the leading bit is
+ always on, so that we don't confuse this format with the
+ others (assuming ASCII bytes of 8 bits or more). */
+
+ int signbit;
+ uintmax_t topbits;
+
+ signbit = *where & (1 << (LG_256 - 2));
+ topbits =
+ (((uintmax_t) - signbit) << (CHAR_BIT * sizeof (uintmax_t) - LG_256 - (LG_256 - 2)));
+
+ value = (*where++ & ((1 << (LG_256 - 2)) - 1)) - signbit;
+
+ while (TRUE)
+ {
+ value = (value << LG_256) + (unsigned char) *where++;
+ if (where == lim)
+ break;
+
+ if (((value << LG_256 >> LG_256) | topbits) != value)
+ return (-1);
+ }
+
+ negative = signbit != 0;
+ if (negative)
+ value = -value;
+ }
+
+ if (where != lim && *where != '\0' && !isspace ((unsigned char) *where))
+ return (-1);
+
+ if (value <= (negative ? minus_minval : maxval))
+ return tar_represent_uintmax (negative ? -value : value);
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+off_from_header (const char *p, size_t s)
+{
+ /* Negative offsets are not allowed in tar files, so invoke
+ from_header with minimum value 0, not TYPE_MINIMUM (off_t). */
+ return tar_from_header (p, s, "off_t", 0, TYPE_MAXIMUM (off_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Return the location of the next available input or output block.
+ * Return NULL for EOF.
+ */
+union block *
+tar_find_next_block (tar_super_t * archive)
+{
+ if (current_block == record_end)
+ {
+ if (hit_eof)
+ return NULL;
+
+ if (!tar_flush_archive (archive))
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ return NULL;
+ }
+
+ if (current_block == record_end)
+ {
+ hit_eof = TRUE;
+ return NULL;
+ }
+ }
+
+ return current_block;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Indicate that we have used all blocks up thru @block.
+ */
+gboolean
+tar_set_next_block_after (union block * block)
+{
+ while (block >= current_block)
+ current_block++;
+
+ /* Do *not* flush the archive here. If we do, the same argument to tar_set_next_block_after()
+ could mean the next block (if the input record is exactly one block long), which is not
+ what is intended. */
+
+ return !(current_block > record_end);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Compute and return the block ordinal at current_block.
+ */
+off_t
+tar_current_block_ordinal (const tar_super_t * archive)
+{
+ return record_start_block + (current_block - archive->record_start);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Skip over @size bytes of data in blocks in the archive.
+ */
+gboolean
+tar_skip_file (tar_super_t * archive, off_t size)
+{
+ union block *x;
+ off_t nblk;
+
+ nblk = tar_seek_archive (archive, size);
+ if (nblk >= 0)
+ size -= nblk * BLOCKSIZE;
+
+ while (size > 0)
+ {
+ x = tar_find_next_block (archive);
+ if (x == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (x);
+ size -= BLOCKSIZE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */