mirror of https://github.com/MidnightCommander/mc
Merge branch '1952_tar'
* 1952_tar: tar: add support of extended headers. struct vfs_s_inode: add the 'user_data' member. tar: prepare to support the POSIX extended headers. tar: refactoring of archive reading. (tar_read_header): move entry/inode creation to new function tar_insert_entry(). tar: use separate structure to store various file info. (tar_read_header): minor optimization. (tar_read_header): rename variable. (tar_find_next_block): rename from tar_get_next_block. src/vfs/tar/tar.c: remove period from error messages. (tar_read_header): rename variables. (tar_get_next_block): use file descriptor from archive descriptor. (tar_open_archive_int): return status of tar archive open src/vfs/tar/tar.c: clarify support of OLDGNU format. (tar_decode_header): do not decode size here. Ticket #1952: tar: support long file names in archive.
This commit is contained in:
commit
e5911c1ef5
|
@ -255,6 +255,8 @@ AC_TYPE_MODE_T
|
|||
gl_PROMOTED_TYPE_MODE_T
|
||||
AC_TYPE_PID_T
|
||||
AC_TYPE_UID_T
|
||||
AC_CHECK_TYPE([major_t], [], [AC_DEFINE([major_t], [int], [Type of major device numbers.])])
|
||||
AC_CHECK_TYPE([minor_t], [], [AC_DEFINE([minor_t], [int], [Type of minor device numbers.])])
|
||||
|
||||
AC_STRUCT_ST_BLOCKS
|
||||
AC_CHECK_MEMBERS([struct stat.st_blksize, struct stat.st_rdev, struct stat.st_mtim])
|
||||
|
|
|
@ -95,6 +95,7 @@ struct vfs_s_inode
|
|||
char *localname; /* Filename of local file, if we have one */
|
||||
gint64 timestamp; /* Subclass specific */
|
||||
off_t data_offset; /* Subclass specific */
|
||||
void *user_data; /* Subclass specific */
|
||||
};
|
||||
|
||||
/* Data associated with an open file */
|
||||
|
|
|
@ -4,4 +4,7 @@ AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
|
|||
noinst_LTLIBRARIES = libvfs-tar.la
|
||||
|
||||
libvfs_tar_la_SOURCES = \
|
||||
tar-internal.c tar-internal.h \
|
||||
tar-sparse.c \
|
||||
tar-xheader.c \
|
||||
tar.c tar.h
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
|
@ -0,0 +1,351 @@
|
|||
|
||||
#ifndef MC__VFS_TAR_INTERNAL_H
|
||||
#define MC__VFS_TAR_INTERNAL_H
|
||||
|
||||
#include <inttypes.h> /* (u)intmax_t */
|
||||
#include <limits.h> /* CHAR_BIT, INT_MAX, etc */
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "lib/vfs/xdirentry.h" /* vfs_s_super */
|
||||
|
||||
/*** typedefs(not structures) and defined constants **********************************************/
|
||||
|
||||
/* tar files are made in basic blocks of this size. */
|
||||
#define BLOCKSIZE 512
|
||||
|
||||
#define DEFAULT_BLOCKING 20
|
||||
|
||||
/* Sparse files are not supported in POSIX ustar format. For sparse files
|
||||
with a POSIX header, a GNU extra header is provided which holds overall
|
||||
sparse information and a few sparse descriptors. When an old GNU header
|
||||
replaces both the POSIX header and the GNU extra header, it holds some
|
||||
sparse descriptors too. Whether POSIX or not, if more sparse descriptors
|
||||
are still needed, they are put into as many successive sparse headers as
|
||||
necessary. The following constants tell how many sparse descriptors fit
|
||||
in each kind of header able to hold them. */
|
||||
|
||||
#define SPARSES_IN_EXTRA_HEADER 16
|
||||
#define SPARSES_IN_OLDGNU_HEADER 4
|
||||
#define SPARSES_IN_SPARSE_HEADER 21
|
||||
|
||||
#define SPARSES_IN_STAR_HEADER 4
|
||||
#define SPARSES_IN_STAR_EXT_HEADER 21
|
||||
|
||||
/* *BEWARE* *BEWARE* *BEWARE* that the following information is still
|
||||
boiling, and may change. Even if the OLDGNU format description should be
|
||||
accurate, the so-called GNU format is not yet fully decided. It is
|
||||
surely meant to use only extensions allowed by POSIX, but the sketch
|
||||
below repeats some ugliness from the OLDGNU format, which should rather
|
||||
go away. Sparse files should be saved in such a way that they do *not*
|
||||
require two passes at archive creation time. Huge files get some POSIX
|
||||
fields to overflow, alternate solutions have to be sought for this. */
|
||||
|
||||
/* This is a dir entry that contains the names of files that were in the
|
||||
dir at the time the dump was made. */
|
||||
#define GNUTYPE_DUMPDIR 'D'
|
||||
|
||||
/* Identifies the *next* file on the tape as having a long linkname. */
|
||||
#define GNUTYPE_LONGLINK 'K'
|
||||
|
||||
/* Identifies the *next* file on the tape as having a long name. */
|
||||
#define GNUTYPE_LONGNAME 'L'
|
||||
|
||||
/* Solaris extended header */
|
||||
#define SOLARIS_XHDTYPE 'X'
|
||||
|
||||
#define GNUTYPE_SPARSE 'S'
|
||||
|
||||
|
||||
/* These macros work even on ones'-complement hosts (!).
|
||||
The extra casts work around some compiler bugs. */
|
||||
#define TYPE_SIGNED(t) (!((t) 0 < (t) (-1)))
|
||||
#define TYPE_MINIMUM(t) (TYPE_SIGNED (t) ? ~(t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)
|
||||
#define TYPE_MAXIMUM(t) (~(t) 0 - TYPE_MINIMUM (t))
|
||||
|
||||
#define OFF_FROM_HEADER(where) off_from_header (where, sizeof (where))
|
||||
|
||||
#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
|
||||
|
||||
/*** enums ***************************************************************************************/
|
||||
|
||||
/*** structures declarations (and typedefs of structures)*****************************************/
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
|
||||
/* POSIX header */
|
||||
struct posix_header
|
||||
{ /* byte offset */
|
||||
char name[100]; /* 0 */
|
||||
char mode[8]; /* 100 */
|
||||
char uid[8]; /* 108 */
|
||||
char gid[8]; /* 116 */
|
||||
char size[12]; /* 124 */
|
||||
char mtime[12]; /* 136 */
|
||||
char chksum[8]; /* 148 */
|
||||
char typeflag; /* 156 */
|
||||
char linkname[100]; /* 157 */
|
||||
char magic[6]; /* 257 */
|
||||
char version[2]; /* 263 */
|
||||
char uname[32]; /* 265 */
|
||||
char gname[32]; /* 297 */
|
||||
char devmajor[8]; /* 329 */
|
||||
char devminor[8]; /* 337 */
|
||||
char prefix[155]; /* 345 */
|
||||
/* 500 */
|
||||
};
|
||||
|
||||
/* Descriptor for a single file hole */
|
||||
struct sparse
|
||||
{ /* byte offset */
|
||||
/* cppcheck-suppress unusedStructMember */
|
||||
char offset[12]; /* 0 */
|
||||
/* cppcheck-suppress unusedStructMember */
|
||||
char numbytes[12]; /* 12 */
|
||||
/* 24 */
|
||||
};
|
||||
|
||||
/* Extension header for sparse files, used immediately after the GNU extra
|
||||
header, and used only if all sparse information cannot fit into that
|
||||
extra header. There might even be many such extension headers, one after
|
||||
the other, until all sparse information has been recorded. */
|
||||
struct sparse_header
|
||||
{ /* byte offset */
|
||||
struct sparse sp[SPARSES_IN_SPARSE_HEADER];
|
||||
/* 0 */
|
||||
char isextended; /* 504 */
|
||||
/* 505 */
|
||||
};
|
||||
|
||||
/* The old GNU format header conflicts with POSIX format in such a way that
|
||||
POSIX archives may fool old GNU tar's, and POSIX tar's might well be
|
||||
fooled by old GNU tar archives. An old GNU format header uses the space
|
||||
used by the prefix field in a POSIX header, and cumulates information
|
||||
normally found in a GNU extra header. With an old GNU tar header, we
|
||||
never see any POSIX header nor GNU extra header. Supplementary sparse
|
||||
headers are allowed, however. */
|
||||
struct oldgnu_header
|
||||
{ /* byte offset */
|
||||
char unused_pad1[345]; /* 0 */
|
||||
char atime[12]; /* 345 Incr. archive: atime of the file */
|
||||
char ctime[12]; /* 357 Incr. archive: ctime of the file */
|
||||
char offset[12]; /* 369 Multivolume archive: the offset of start of this volume */
|
||||
char longnames[4]; /* 381 Not used */
|
||||
char unused_pad2; /* 385 */
|
||||
struct sparse sp[SPARSES_IN_OLDGNU_HEADER];
|
||||
/* 386 */
|
||||
char isextended; /* 482 Sparse file: Extension sparse header follows */
|
||||
char realsize[12]; /* 483 Sparse file: Real size */
|
||||
/* 495 */
|
||||
};
|
||||
|
||||
/* J@"org Schilling star header */
|
||||
struct star_header
|
||||
{ /* byte offset */
|
||||
char name[100]; /* 0 */
|
||||
char mode[8]; /* 100 */
|
||||
char uid[8]; /* 108 */
|
||||
char gid[8]; /* 116 */
|
||||
char size[12]; /* 124 */
|
||||
char mtime[12]; /* 136 */
|
||||
char chksum[8]; /* 148 */
|
||||
char typeflag; /* 156 */
|
||||
char linkname[100]; /* 157 */
|
||||
char magic[6]; /* 257 */
|
||||
char version[2]; /* 263 */
|
||||
char uname[32]; /* 265 */
|
||||
char gname[32]; /* 297 */
|
||||
char devmajor[8]; /* 329 */
|
||||
char devminor[8]; /* 337 */
|
||||
char prefix[131]; /* 345 */
|
||||
char atime[12]; /* 476 */
|
||||
char ctime[12]; /* 488 */
|
||||
/* 500 */
|
||||
};
|
||||
|
||||
struct star_in_header
|
||||
{
|
||||
char fill[345]; /* 0 Everything that is before t_prefix */
|
||||
char prefix[1]; /* 345 t_name prefix */
|
||||
char fill2; /* 346 */
|
||||
char fill3[8]; /* 347 */
|
||||
char isextended; /* 355 */
|
||||
struct sparse sp[SPARSES_IN_STAR_HEADER]; /* 356 */
|
||||
char realsize[12]; /* 452 Actual size of the file */
|
||||
char offset[12]; /* 464 Offset of multivolume contents */
|
||||
char atime[12]; /* 476 */
|
||||
char ctime[12]; /* 488 */
|
||||
char mfill[8]; /* 500 */
|
||||
char xmagic[4]; /* 508 "tar" */
|
||||
};
|
||||
|
||||
struct star_ext_header
|
||||
{
|
||||
struct sparse sp[SPARSES_IN_STAR_EXT_HEADER];
|
||||
char isextended;
|
||||
};
|
||||
|
||||
/* *INDENT-ON* */
|
||||
|
||||
/* tar Header Block, overall structure */
|
||||
union block
|
||||
{
|
||||
char buffer[BLOCKSIZE];
|
||||
struct posix_header header;
|
||||
struct star_header star_header;
|
||||
struct oldgnu_header oldgnu_header;
|
||||
struct sparse_header sparse_header;
|
||||
struct star_in_header star_in_header;
|
||||
struct star_ext_header star_ext_header;
|
||||
};
|
||||
|
||||
/* Information about a sparse file */
|
||||
struct sp_array
|
||||
{
|
||||
off_t offset; /* chunk offset in file */
|
||||
off_t numbytes; /* length of chunk */
|
||||
off_t arch_offset; /* chunk offset in archive */
|
||||
};
|
||||
|
||||
enum dump_status
|
||||
{
|
||||
dump_status_ok,
|
||||
dump_status_short,
|
||||
dump_status_fail,
|
||||
dump_status_not_implemented
|
||||
};
|
||||
|
||||
enum archive_format
|
||||
{
|
||||
TAR_UNKNOWN = 0, /**< format to be decided later */
|
||||
TAR_V7, /**< old V7 tar format */
|
||||
TAR_OLDGNU, /**< GNU format as per before tar 1.12 */
|
||||
TAR_USTAR, /**< POSIX.1-1988 (ustar) format */
|
||||
TAR_POSIX, /**< POSIX.1-2001 format */
|
||||
TAR_STAR, /**< star format defined in 1994 */
|
||||
TAR_GNU /**< almost same as OLDGNU_FORMAT */
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct vfs_s_super base; /* base class */
|
||||
|
||||
int fd;
|
||||
struct stat st;
|
||||
enum archive_format type; /**< type of the archive */
|
||||
union block *record_start; /**< start of record of archive */
|
||||
} tar_super_t;
|
||||
|
||||
struct xheader
|
||||
{
|
||||
size_t size;
|
||||
char *buffer;
|
||||
};
|
||||
|
||||
struct tar_stat_info
|
||||
{
|
||||
char *orig_file_name; /**< name of file read from the archive header */
|
||||
char *file_name; /**< name of file for the current archive entry after being normalized */
|
||||
char *link_name; /**< name of link for the current archive entry */
|
||||
#if 0
|
||||
char *uname; /**< user name of owner */
|
||||
char *gname; /**< group name of owner */
|
||||
#endif
|
||||
struct stat stat; /**< regular filesystem stat */
|
||||
|
||||
/* stat() doesn't always have access, data modification, and status
|
||||
change times in a convenient form, so store them separately. */
|
||||
struct timespec atime;
|
||||
struct timespec mtime;
|
||||
struct timespec ctime;
|
||||
|
||||
off_t archive_file_size; /**< size of file as stored in the archive.
|
||||
Equals stat.st_size for non-sparse files */
|
||||
gboolean is_sparse; /**< is the file sparse */
|
||||
|
||||
/* For sparse files */
|
||||
unsigned int sparse_major;
|
||||
unsigned int sparse_minor;
|
||||
GArray *sparse_map; /**< array of struct sp_array */
|
||||
|
||||
off_t real_size; /**< real size of sparse file */
|
||||
gboolean real_size_set; /**< TRUE when GNU.sparse.realsize is set in archived file */
|
||||
|
||||
gboolean sparse_name_done; /**< TRUE if 'GNU.sparse.name' header was processed pax header parsing.
|
||||
Following 'path' header (lower priority) will be ignored. */
|
||||
|
||||
/* Extended headers */
|
||||
struct xheader xhdr;
|
||||
|
||||
/* For dumpdirs */
|
||||
gboolean is_dumpdir; /**< is the member a dumpdir? */
|
||||
gboolean skipped; /**< the member contents is already read (for GNUTYPE_DUMPDIR) */
|
||||
char *dumpdir; /**< contents of the dump directory */
|
||||
};
|
||||
|
||||
/*** global variables defined in .c file *********************************************************/
|
||||
|
||||
extern const int blocking_factor;
|
||||
extern const size_t record_size;
|
||||
|
||||
extern union block *record_end; /* last+1 block of archive record */
|
||||
extern union block *current_block; /* current block of archive */
|
||||
extern off_t record_start_block; /* block ordinal at record_start */
|
||||
|
||||
extern union block *current_header;
|
||||
|
||||
/* Have we hit EOF yet? */
|
||||
extern gboolean hit_eof;
|
||||
|
||||
extern struct tar_stat_info current_stat_info;
|
||||
|
||||
/*** declarations of public functions ************************************************************/
|
||||
|
||||
/* tar-internal.c */
|
||||
void tar_base64_init (void);
|
||||
void tar_assign_string (char **string, char *value);
|
||||
void tar_assign_string_dup (char **string, const char *value);
|
||||
void tar_assign_string_dup_n (char **string, const char *value, size_t n);
|
||||
intmax_t tar_from_header (const char *where0, size_t digs, char const *type, intmax_t minval,
|
||||
uintmax_t maxval, gboolean octal_only);
|
||||
off_t off_from_header (const char *p, size_t s);
|
||||
union block *tar_find_next_block (tar_super_t * archive);
|
||||
gboolean tar_set_next_block_after (union block *block);
|
||||
off_t tar_current_block_ordinal (const tar_super_t * archive);
|
||||
gboolean tar_skip_file (tar_super_t * archive, off_t size);
|
||||
|
||||
/* tar-sparse.c */
|
||||
gboolean tar_sparse_member_p (tar_super_t * archive, struct tar_stat_info *st);
|
||||
gboolean tar_sparse_fixup_header (tar_super_t * archive, struct tar_stat_info *st);
|
||||
enum dump_status tar_sparse_skip_file (tar_super_t * archive, struct tar_stat_info *st);
|
||||
|
||||
/* tar-xheader.c */
|
||||
gboolean tar_xheader_decode (struct tar_stat_info *st);
|
||||
gboolean tar_xheader_read (tar_super_t * archive, struct xheader *xhdr, union block *header,
|
||||
off_t size);
|
||||
gboolean tar_xheader_decode_global (struct xheader *xhdr);
|
||||
void tar_xheader_destroy (struct xheader *xhdr);
|
||||
|
||||
/*** inline functions ****************************************************************************/
|
||||
|
||||
/**
|
||||
* Represent @n using a signed integer I such that (uintmax_t) I == @n.
|
||||
With a good optimizing compiler, this is equivalent to (intmax_t) i
|
||||
and requires zero machine instructions. */
|
||||
#if !(UINTMAX_MAX / 2 <= INTMAX_MAX)
|
||||
#error "tar_represent_uintmax() returns intmax_t to represent uintmax_t"
|
||||
#endif
|
||||
static inline intmax_t
|
||||
tar_represent_uintmax (uintmax_t n)
|
||||
{
|
||||
intmax_t nd;
|
||||
|
||||
if (n <= INTMAX_MAX)
|
||||
return n;
|
||||
|
||||
/* Avoid signed integer overflow on picky platforms. */
|
||||
nd = n - INTMAX_MIN;
|
||||
return nd + INTMAX_MIN;
|
||||
}
|
||||
|
||||
#endif /* MC__VFS_TAR_INTERNAL_H */
|
|
@ -0,0 +1,763 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
#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 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);
|
||||
|
||||
for (i = 0; i < u; 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)
|
||||
|| 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;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
File diff suppressed because it is too large
Load Diff
1172
src/vfs/tar/tar.c
1172
src/vfs/tar/tar.c
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue