/* Functions for dealing with sparse files
- Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+ Copyright (C) 2003, 2004, 2005, 2006, 2007, 2010 Free Software
+ Foundation, Inc.
This program 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 2, or (at your option) any later
+ Free Software Foundation; either version 3, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but
struct tar_stat_info *st = file->stat_info;
int fd = file->fd;
char buffer[BLOCKSIZE];
- size_t count;
+ size_t count = 0;
off_t offset = 0;
struct sp_array sp = {0, 0};
- if (!lseek_or_error (file, 0))
- return false;
-
st->archive_file_size = 0;
-
- if (!tar_sparse_scan (file, scan_begin, NULL))
- return false;
- while ((count = safe_read (fd, buffer, sizeof buffer)) != 0
- && count != SAFE_READ_ERROR)
+ if (ST_NBLOCKS (st->stat) == 0)
+ offset = st->stat.st_size;
+ else
{
- /* Analyze the block. */
- if (zero_block_p (buffer, count))
+ if (!tar_sparse_scan (file, scan_begin, NULL))
+ return false;
+
+ while ((count = safe_read (fd, buffer, sizeof buffer)) != 0
+ && count != SAFE_READ_ERROR)
{
- if (sp.numbytes)
+ /* Analyze the block. */
+ if (zero_block_p (buffer, count))
{
- sparse_add_map (st, &sp);
- sp.numbytes = 0;
- if (!tar_sparse_scan (file, scan_block, NULL))
+ if (sp.numbytes)
+ {
+ sparse_add_map (st, &sp);
+ sp.numbytes = 0;
+ if (!tar_sparse_scan (file, scan_block, NULL))
+ return false;
+ }
+ }
+ else
+ {
+ if (sp.numbytes == 0)
+ sp.offset = offset;
+ sp.numbytes += count;
+ st->archive_file_size += count;
+ if (!tar_sparse_scan (file, scan_block, buffer))
return false;
}
- }
- else
- {
- if (sp.numbytes == 0)
- sp.offset = offset;
- sp.numbytes += count;
- st->archive_file_size += count;
- if (!tar_sparse_scan (file, scan_block, buffer))
- return false;
- }
- offset += count;
+ offset += count;
+ }
}
if (sp.numbytes == 0)
memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read);
bytes_left -= bytes_read;
file->dumped_size += bytes_read;
- mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
set_next_block_after (blk);
}
static bool
sparse_extract_region (struct tar_sparse_file *file, size_t i)
{
- size_t write_size;
+ off_t write_size;
if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
return false;
{
size_t i;
- mv_begin (file.stat_info);
+ mv_begin_write (file.stat_info->file_name,
+ file.stat_info->stat.st_size,
+ file.stat_info->archive_file_size - file.dumped_size);
for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
rc = tar_sparse_dump_region (&file, i);
- mv_end ();
}
}
return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
}
-/* Returns true if the file represented by stat is a sparse one */
-bool
-sparse_file_p (struct tar_stat_info *st)
-{
- return (ST_NBLOCKS (st->stat)
- < (st->stat.st_size / ST_NBLOCKSIZE
- + (st->stat.st_size % ST_NBLOCKSIZE != 0)));
-}
-
bool
sparse_member_p (struct tar_stat_info *st)
{
file.fd = -1;
rc = tar_sparse_decode_header (&file);
- skip_file (file.stat_info->archive_file_size);
+ skip_file (file.stat_info->archive_file_size - file.dumped_size);
return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
}
static bool
check_data_region (struct tar_sparse_file *file, size_t i)
{
- size_t size_left;
+ off_t size_left;
if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
return false;
size_left = file->stat_info->sparse_map[i].numbytes;
mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
-
+
while (size_left > 0)
{
size_t bytes_read;
file.stat_info = st;
file.fd = fd;
file.seekable = true; /* File *must* be seekable for compare to work */
-
+
rc = tar_sparse_decode_header (&file);
- mv_begin (st);
+ mv_begin_read (st);
for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
{
rc = check_sparse_region (&file,
if (!rc)
skip_file (file.stat_info->archive_file_size - file.dumped_size);
mv_end ();
-
+
tar_sparse_done (&file);
return rc;
}
if (s->numbytes[0] == '\0')
return add_finish;
sp.offset = OFF_FROM_HEADER (s->offset);
- sp.numbytes = SIZE_FROM_HEADER (s->numbytes);
+ sp.numbytes = OFF_FROM_HEADER (s->numbytes);
if (sp.offset < 0
+ || sp.offset + sp.numbytes < 0
|| file->stat_info->stat.st_size < sp.offset + sp.numbytes
|| file->stat_info->archive_file_size < 0)
return add_fail;
{
OFF_TO_CHARS (file->stat_info->sparse_map[*pindex].offset,
sp->offset);
- SIZE_TO_CHARS (file->stat_info->sparse_map[*pindex].numbytes,
- sp->numbytes);
+ OFF_TO_CHARS (file->stat_info->sparse_map[*pindex].numbytes,
+ sp->numbytes);
}
}
\f
/* 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.
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]..."
+ 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
* 1.0
- Starting from this version, the exact sparse format version is specified explicitely
- in the header using the following variables:
+ Starting from this version, the exact sparse format version is specified
+ explicitely in the header using the following variables:
- GNU.sparse.major Major version
+ 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.
+ 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 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
+ 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 bool
char nbuf[UINTMAX_STRSIZE_BOUND];
struct sp_array *map = file->stat_info->sparse_map;
char *save_file_name = NULL;
-
+
/* Store the real file size */
xheader_store ("GNU.sparse.size", file->stat_info, NULL);
xheader_store ("GNU.sparse.numblocks", file->stat_info, NULL);
-
+
if (xheader_keyword_deleted_p ("GNU.sparse.map")
|| tar_sparse_minor == 0)
{
file->stat_info->file_name = xheader_format_name (file->stat_info,
"%d/GNUSparseFile.%p/%f", 0);
- xheader_string_begin ();
+ xheader_string_begin (&file->stat_info->xhdr);
for (i = 0; i < file->stat_info->sparse_map_avail; i++)
{
if (i)
- xheader_string_add (",");
- xheader_string_add (umaxtostr (map[i].offset, nbuf));
- xheader_string_add (",");
- xheader_string_add (umaxtostr (map[i].numbytes, nbuf));
+ xheader_string_add (&file->stat_info->xhdr, ",");
+ xheader_string_add (&file->stat_info->xhdr,
+ umaxtostr (map[i].offset, nbuf));
+ xheader_string_add (&file->stat_info->xhdr, ",");
+ xheader_string_add (&file->stat_info->xhdr,
+ umaxtostr (map[i].numbytes, nbuf));
+ }
+ if (!xheader_string_end (&file->stat_info->xhdr,
+ "GNU.sparse.map"))
+ {
+ free (file->stat_info->file_name);
+ file->stat_info->file_name = save_file_name;
+ return false;
}
- xheader_string_end ("GNU.sparse.map");
}
blk = start_header (file->stat_info);
/* Store the effective (shrunken) file size */
#define COPY_STRING(b,dst,src) do \
{ \
char *endp = b->buffer + BLOCKSIZE; \
- char *srcp = src; \
+ char const *srcp = src; \
while (*srcp) \
{ \
if (dst == endp) \
} \
*dst++ = *srcp++; \
} \
- } while (0)
+ } while (0)
/* Compute stored file size */
p = umaxtostr (file->stat_info->sparse_map_avail, nbuf);
}
size = (size + BLOCKSIZE - 1) / BLOCKSIZE;
file->stat_info->archive_file_size += size * BLOCKSIZE;
-
+ file->dumped_size += size * BLOCKSIZE;
+
/* Store sparse file identification */
xheader_store ("GNU.sparse.major", file->stat_info, NULL);
xheader_store ("GNU.sparse.minor", file->stat_info, NULL);
xheader_store ("GNU.sparse.name", file->stat_info, NULL);
xheader_store ("GNU.sparse.realsize", file->stat_info, NULL);
-
+
file->stat_info->file_name = xheader_format_name (file->stat_info,
"%d/GNUSparseFile.%p/%f", 0);
{
file->stat_info->sparse_major = tar_sparse_major;
file->stat_info->sparse_minor = tar_sparse_minor;
-
- if (file->stat_info->sparse_major == 0)
- pax_dump_header_0 (file);
- else
- pax_dump_header_1 (file);
+
+ return (file->stat_info->sparse_major == 0) ?
+ pax_dump_header_0 (file) : pax_dump_header_1 (file);
}
static bool
if (!ISDIGIT (*arg))
return false;
-
+
u = strtoumax (arg, &arg_lim, 10);
if (! (u <= maxval && errno != ERANGE) || *arg_lim)
return false;
-
+
*num = u;
return true;
}
{ \
if (dst == buf + UINTMAX_STRSIZE_BOUND -1) \
{ \
- ERROR ((0, 0, _("%s: numeric overflow in sparse archive member"), \
+ ERROR ((0, 0, _("%s: numeric overflow in sparse archive member"), \
file->stat_info->orig_file_name)); \
return false; \
} \
- if (src == endp) \
+ if (src == endp) \
{ \
set_next_block_after (b); \
- b = find_next_block (); \
+ file->dumped_size += BLOCKSIZE; \
+ b = find_next_block (); \
src = b->buffer; \
endp = b->buffer + BLOCKSIZE; \
} \
} \
while (*dst++ != '\n'); \
dst[-1] = 0; \
- } while (0)
+ } while (0)
set_next_block_after (current_header);
+ file->dumped_size += BLOCKSIZE;
blk = find_next_block ();
p = blk->buffer;
COPY_BUF (blk,nbuf,p);
if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
{
- ERROR ((0, 0, _("%s: malformed sparse archive member"),
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
file->stat_info->orig_file_name));
return false;
}
for (i = 0; i < file->stat_info->sparse_map_size; i++)
{
struct sp_array sp;
-
+
COPY_BUF (blk,nbuf,p);
if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
{
- ERROR ((0, 0, _("%s: malformed sparse archive member"),
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
file->stat_info->orig_file_name));
return false;
}
sp.offset = u;
COPY_BUF (blk,nbuf,p);
- if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
{
- ERROR ((0, 0, _("%s: malformed sparse archive member"),
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
file->stat_info->orig_file_name));
return false;
}
}
set_next_block_after (blk);
}
-
+
return true;
}
pax_sparse_member_p,
pax_dump_header,
NULL,
- pax_decode_header,
+ pax_decode_header,
NULL, /* No scan_block function */
sparse_dump_region,
sparse_extract_region,