mirror of https://github.com/MidnightCommander/mc
tar: add support of extended headers.
Most of the code is taken from GNU tar. Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
This commit is contained in:
parent
290a57b474
commit
8d1427d183
|
@ -5,4 +5,6 @@ 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
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
#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
|
||||
|
@ -48,6 +51,11 @@
|
|||
/* 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. */
|
||||
|
@ -131,6 +139,52 @@ struct oldgnu_header
|
|||
/* 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 */
|
||||
|
@ -138,8 +192,27 @@ 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
|
||||
|
@ -149,6 +222,7 @@ enum archive_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 */
|
||||
};
|
||||
|
||||
|
@ -162,6 +236,12 @@ typedef struct
|
|||
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 */
|
||||
|
@ -178,6 +258,29 @@ struct tar_stat_info
|
|||
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 *********************************************************/
|
||||
|
@ -211,6 +314,18 @@ 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 ****************************************************************************/
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
@ -159,6 +159,12 @@ tar_stat_destroy (struct tar_stat_info *st)
|
|||
g_free (st->uname);
|
||||
g_free (st->gname);
|
||||
#endif
|
||||
if (st->sparse_map != NULL)
|
||||
{
|
||||
g_array_free (st->sparse_map, TRUE);
|
||||
st->sparse_map = NULL;
|
||||
}
|
||||
tar_xheader_destroy (&st->xhdr);
|
||||
memset (st, 0, sizeof (*st));
|
||||
}
|
||||
|
||||
|
@ -249,29 +255,51 @@ uintmax_from_header (const char *p, size_t s)
|
|||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static void
|
||||
tar_calc_sparse_offsets (struct vfs_s_inode *inode)
|
||||
{
|
||||
off_t begin = inode->data_offset;
|
||||
GArray *sm = (GArray *) inode->user_data;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sm->len; i++)
|
||||
{
|
||||
struct sp_array *sp;
|
||||
|
||||
sp = &g_array_index (sm, struct sp_array, i);
|
||||
sp->arch_offset = begin;
|
||||
begin += BLOCKSIZE * (sp->numbytes / BLOCKSIZE + sp->numbytes % BLOCKSIZE);
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static gboolean
|
||||
tar_skip_member (tar_super_t * archive, struct vfs_s_inode *inode)
|
||||
{
|
||||
char save_typeflag = current_header->header.typeflag;
|
||||
char save_typeflag;
|
||||
|
||||
if (current_stat_info.skipped)
|
||||
return TRUE;
|
||||
|
||||
save_typeflag = current_header->header.typeflag;
|
||||
|
||||
tar_set_next_block_after (current_header);
|
||||
|
||||
if (archive->type == TAR_OLDGNU && current_header->oldgnu_header.isextended)
|
||||
if (current_stat_info.is_sparse)
|
||||
{
|
||||
union block *exhdr;
|
||||
if (inode != NULL)
|
||||
inode->data_offset = BLOCKSIZE * tar_current_block_ordinal (archive);
|
||||
|
||||
do
|
||||
{
|
||||
exhdr = tar_find_next_block (archive);
|
||||
if (exhdr == NULL)
|
||||
return FALSE;
|
||||
(void) tar_sparse_skip_file (archive, ¤t_stat_info);
|
||||
|
||||
tar_set_next_block_after (exhdr);
|
||||
}
|
||||
while (exhdr->sparse_header.isextended != 0);
|
||||
/* use vfs_s_inode::user_data to keep the sparse map */
|
||||
inode->user_data = current_stat_info.sparse_map;
|
||||
current_stat_info.sparse_map = NULL;
|
||||
|
||||
tar_calc_sparse_offsets (inode);
|
||||
}
|
||||
|
||||
if (save_typeflag != DIRTYPE)
|
||||
else if (save_typeflag != DIRTYPE)
|
||||
{
|
||||
if (inode != NULL)
|
||||
inode->data_offset = BLOCKSIZE * tar_current_block_ordinal (archive);
|
||||
|
@ -359,7 +387,11 @@ tar_decode_header (union block *header, tar_super_t * arch)
|
|||
{
|
||||
if (strcmp (header->header.magic, TMAGIC) == 0)
|
||||
{
|
||||
if (header->header.typeflag == XGLTYPE)
|
||||
if (header->star_header.prefix[130] == 0 && isodigit (header->star_header.atime[0])
|
||||
&& header->star_header.atime[11] == ' ' && isodigit (header->star_header.ctime[0])
|
||||
&& header->star_header.ctime[11] == ' ')
|
||||
arch->type = TAR_STAR;
|
||||
else if (current_stat_info.xhdr.buffer != NULL)
|
||||
arch->type = TAR_POSIX;
|
||||
else
|
||||
arch->type = TAR_USTAR;
|
||||
|
@ -468,6 +500,11 @@ tar_fill_stat (struct vfs_s_super *archive, union block *header)
|
|||
current_stat_info.atime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.atime);
|
||||
current_stat_info.ctime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.ctime);
|
||||
}
|
||||
else if (arch->type == TAR_STAR)
|
||||
{
|
||||
current_stat_info.atime.tv_sec = TIME_FROM_HEADER (header->star_header.atime);
|
||||
current_stat_info.ctime.tv_sec = TIME_FROM_HEADER (header->star_header.ctime);
|
||||
}
|
||||
else
|
||||
current_stat_info.atime = current_stat_info.ctime = start_time;
|
||||
|
||||
|
@ -479,6 +516,16 @@ tar_fill_stat (struct vfs_s_super *archive, union block *header)
|
|||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static void
|
||||
tar_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
|
||||
{
|
||||
/* free sparse_map */
|
||||
if (ino->user_data != NULL)
|
||||
g_array_free ((GArray *) ino->user_data, TRUE);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static read_header
|
||||
tar_insert_entry (struct vfs_class *me, struct vfs_s_super *archive, union block *header,
|
||||
struct vfs_s_inode **inode)
|
||||
|
@ -649,13 +696,37 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive)
|
|||
|
||||
*bp = '\0';
|
||||
}
|
||||
else if (header->header.typeflag == XHDTYPE || header->header.typeflag == XGLTYPE)
|
||||
else if (header->header.typeflag == XHDTYPE || header->header.typeflag == SOLARIS_XHDTYPE)
|
||||
{
|
||||
/* Skip over pax extended header and global extended header records. */
|
||||
if (arch->type == TAR_UNKNOWN)
|
||||
arch->type = TAR_POSIX;
|
||||
status = tar_skip_member (arch, NULL) ? HEADER_SUCCESS : HEADER_FAILURE;
|
||||
goto ret;
|
||||
if (!tar_xheader_read
|
||||
(arch, ¤t_stat_info.xhdr, header, OFF_FROM_HEADER (header->header.size)))
|
||||
{
|
||||
message (D_ERROR, MSG_ERROR, _("Unexpected EOF on archive file"));
|
||||
status = HEADER_FAILURE;
|
||||
goto ret;
|
||||
}
|
||||
}
|
||||
else if (header->header.typeflag == XGLTYPE)
|
||||
{
|
||||
struct xheader xhdr;
|
||||
gboolean ok;
|
||||
|
||||
if (arch->type == TAR_UNKNOWN)
|
||||
arch->type = TAR_POSIX;
|
||||
|
||||
memset (&xhdr, 0, sizeof (xhdr));
|
||||
tar_xheader_read (arch, &xhdr, header, OFF_FROM_HEADER (header->header.size));
|
||||
ok = tar_xheader_decode_global (&xhdr);
|
||||
tar_xheader_destroy (&xhdr);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
|
||||
status = HEADER_FAILURE;
|
||||
goto ret;
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
@ -663,7 +734,7 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive)
|
|||
|
||||
{
|
||||
static union block *recent_long_name = NULL, *recent_long_link = NULL;
|
||||
struct posix_header const *h = ¤t_header->header;
|
||||
struct posix_header const *h = &header->header;
|
||||
char *file_name = NULL;
|
||||
char *link_name;
|
||||
struct vfs_s_inode *inode = NULL;
|
||||
|
@ -683,15 +754,8 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive)
|
|||
|
||||
/* Don't parse TAR_OLDGNU incremental headers as POSIX prefixes. */
|
||||
if (h->prefix[0] != '\0' && strcmp (h->magic, TMAGIC) == 0)
|
||||
{
|
||||
s1 = g_strndup (h->prefix, sizeof (h->prefix));
|
||||
|
||||
/* Prevent later references to current_header from mistakenly treating this
|
||||
as an old GNU header.
|
||||
This assignment invalidates h->prefix. */
|
||||
current_header->oldgnu_header.isextended = 0;
|
||||
}
|
||||
|
||||
s2 = g_strndup (h->name, sizeof (h->name));
|
||||
|
||||
if (s1 == NULL)
|
||||
|
@ -725,7 +789,31 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive)
|
|||
|
||||
tar_assign_string (¤t_stat_info.link_name, link_name);
|
||||
|
||||
tar_set_next_block_after (current_header);
|
||||
if (current_stat_info.xhdr.buffer != NULL && !tar_xheader_decode (¤t_stat_info))
|
||||
{
|
||||
status = HEADER_FAILURE;
|
||||
goto ret;
|
||||
}
|
||||
|
||||
if (tar_sparse_member_p (arch, ¤t_stat_info))
|
||||
{
|
||||
if (!tar_sparse_fixup_header (arch, ¤t_stat_info))
|
||||
{
|
||||
status = HEADER_FAILURE;
|
||||
goto ret;
|
||||
}
|
||||
|
||||
current_stat_info.is_sparse = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_stat_info.is_sparse = FALSE;
|
||||
|
||||
if (((arch->type == TAR_GNU || arch->type == TAR_OLDGNU)
|
||||
&& current_header->header.typeflag == GNUTYPE_DUMPDIR)
|
||||
|| current_stat_info.dumpdir != NULL)
|
||||
current_stat_info.is_dumpdir = TRUE;
|
||||
}
|
||||
|
||||
status = tar_insert_entry (me, archive, header, &inode);
|
||||
if (status != HEADER_SUCCESS)
|
||||
|
@ -992,6 +1080,145 @@ tar_super_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *pa
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
/* Get indes of current data chunk in a sparse file.
|
||||
*
|
||||
* @param sparse_map map of the sparse file
|
||||
* @param offset offset in the sparse file
|
||||
*
|
||||
* @return an index of ahole or a data chunk
|
||||
* positive: pointer to the data chunk;
|
||||
* negative: pointer to the hole before data chunk;
|
||||
* zero: pointer to the hole after last data chunk
|
||||
*
|
||||
* +--------+--------+-------+--------+-----+-------+--------+---------+
|
||||
* | hole1 | chunk1 | hole2 | chunk2 | ... | holeN | chunkN | holeN+1 |
|
||||
* +--------+--------+-------+--------+-----+-------+--------+---------+
|
||||
* -1 1 -2 2 -N N 0
|
||||
*/
|
||||
|
||||
static ssize_t
|
||||
tar_get_sparse_chunk_idx (const GArray * sparse_map, off_t offset)
|
||||
{
|
||||
size_t k;
|
||||
|
||||
for (k = 1; k <= sparse_map->len; k++)
|
||||
{
|
||||
const struct sp_array *chunk;
|
||||
|
||||
chunk = &g_array_index (sparse_map, struct sp_array, k - 1);
|
||||
|
||||
/* are we in the current chunk? */
|
||||
if (offset >= chunk->offset && offset < chunk->offset + chunk->numbytes)
|
||||
return k;
|
||||
|
||||
/* are we before the current chunk? */
|
||||
if (offset < chunk->offset)
|
||||
return -k;
|
||||
}
|
||||
|
||||
/* after the last chunk */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static ssize_t
|
||||
tar_read_sparse (vfs_file_handler_t * fh, char *buffer, size_t count)
|
||||
{
|
||||
int fd = TAR_SUPER (fh->ino->super)->fd;
|
||||
const GArray *sm = (const GArray *) fh->ino->user_data;
|
||||
ssize_t chunk_idx;
|
||||
const struct sp_array *chunk;
|
||||
off_t remain;
|
||||
ssize_t res;
|
||||
|
||||
chunk_idx = tar_get_sparse_chunk_idx (sm, fh->pos);
|
||||
if (chunk_idx > 0)
|
||||
{
|
||||
/* we are in the chunk -- read data until chunk end */
|
||||
chunk = &g_array_index (sm, struct sp_array, chunk_idx - 1);
|
||||
remain = MIN ((off_t) count, chunk->offset + chunk->numbytes - fh->pos);
|
||||
res = mc_read (fd, buffer, (size_t) remain);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chunk_idx == 0)
|
||||
{
|
||||
/* we are in the hole after last chunk -- return zeros until file end */
|
||||
remain = MIN ((off_t) count, fh->ino->st.st_size - fh->pos);
|
||||
/* FIXME: can remain be negative? */
|
||||
remain = MAX (remain, 0);
|
||||
}
|
||||
else /* chunk_idx < 0 */
|
||||
{
|
||||
/* we are in the hole -- return zeros until next chunk start */
|
||||
chunk = &g_array_index (sm, struct sp_array, -chunk_idx - 1);
|
||||
remain = MIN ((off_t) count, chunk->offset - fh->pos);
|
||||
}
|
||||
|
||||
memset (buffer, 0, (size_t) remain);
|
||||
res = (ssize_t) remain;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static off_t
|
||||
tar_lseek_sparse (vfs_file_handler_t * fh, off_t offset)
|
||||
{
|
||||
off_t saved_offset = offset;
|
||||
int fd = TAR_SUPER (fh->ino->super)->fd;
|
||||
const GArray *sm = (const GArray *) fh->ino->user_data;
|
||||
ssize_t chunk_idx;
|
||||
const struct sp_array *chunk;
|
||||
off_t res;
|
||||
|
||||
chunk_idx = tar_get_sparse_chunk_idx (sm, offset);
|
||||
if (chunk_idx > 0)
|
||||
{
|
||||
/* we are in the chunk */
|
||||
|
||||
chunk = &g_array_index (sm, struct sp_array, chunk_idx - 1);
|
||||
/* offset in the chunk */
|
||||
offset -= chunk->offset;
|
||||
/* offset in the archive */
|
||||
offset += chunk->arch_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* we are in the hole */
|
||||
|
||||
/* we cannot lseek in hole so seek to the hole begin or end */
|
||||
switch (chunk_idx)
|
||||
{
|
||||
case -1:
|
||||
offset = fh->ino->data_offset;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
chunk = &g_array_index (sm, struct sp_array, sm->len - 1);
|
||||
/* FIXME: can we seek beyond tar archive EOF here? */
|
||||
offset = chunk->arch_offset + chunk->numbytes;
|
||||
break;
|
||||
|
||||
default:
|
||||
chunk = &g_array_index (sm, struct sp_array, -chunk_idx - 1);
|
||||
offset = chunk->arch_offset + chunk->numbytes;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res = mc_lseek (fd, offset, SEEK_SET);
|
||||
/* return requested offset in success */
|
||||
if (res == offset)
|
||||
res = saved_offset;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static ssize_t
|
||||
|
@ -999,16 +1226,28 @@ tar_read (void *fh, char *buffer, size_t count)
|
|||
{
|
||||
struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
|
||||
vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
|
||||
off_t begin = file->ino->data_offset;
|
||||
int fd = TAR_SUPER (VFS_FILE_HANDLER_SUPER (fh))->fd;
|
||||
off_t begin = file->pos;
|
||||
ssize_t res;
|
||||
|
||||
if (mc_lseek (fd, begin + file->pos, SEEK_SET) != begin + file->pos)
|
||||
ERRNOR (EIO, -1);
|
||||
if (file->ino->user_data != NULL)
|
||||
{
|
||||
if (tar_lseek_sparse (file, begin) != begin)
|
||||
ERRNOR (EIO, -1);
|
||||
|
||||
count = MIN (count, (size_t) (file->ino->st.st_size - file->pos));
|
||||
res = tar_read_sparse (file, buffer, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
begin += file->ino->data_offset;
|
||||
|
||||
if (mc_lseek (fd, begin, SEEK_SET) != begin)
|
||||
ERRNOR (EIO, -1);
|
||||
|
||||
count = (size_t) MIN ((off_t) count, file->ino->st.st_size - file->pos);
|
||||
res = mc_read (fd, buffer, count);
|
||||
}
|
||||
|
||||
res = mc_read (fd, buffer, count);
|
||||
if (res == -1)
|
||||
ERRNOR (errno, -1);
|
||||
|
||||
|
@ -1045,6 +1284,7 @@ vfs_init_tarfs (void)
|
|||
tarfs_subclass.new_archive = tar_new_archive;
|
||||
tarfs_subclass.open_archive = tar_open_archive;
|
||||
tarfs_subclass.free_archive = tar_free_archive;
|
||||
tarfs_subclass.free_inode = tar_free_inode;
|
||||
tarfs_subclass.fh_open = tar_fh_open;
|
||||
vfs_register_class (vfs_tarfs_ops);
|
||||
|
||||
|
|
Loading…
Reference in New Issue