X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Fsparse.c;h=4e784015e722ad39a69f5b2c2e7975d3ade361ad;hb=HEAD;hp=9680b6052244cbb0904d5fedf9ba671995cbd1da;hpb=ee168310ec4227174ace489bf5f81f8c2f91cde0;p=debian%2Ftar diff --git a/src/sparse.c b/src/sparse.c index 9680b605..4e784015 100644 --- a/src/sparse.c +++ b/src/sparse.c @@ -1,7 +1,6 @@ /* Functions for dealing with sparse files - Copyright (C) 2003, 2004, 2005, 2006, 2007, 2010 Free Software - Foundation, Inc. + Copyright 2003-2007, 2010, 2013-2016 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 @@ -14,8 +13,7 @@ Public License for more details. You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + with this program. If not, see . */ #include #include @@ -210,9 +208,9 @@ sparse_add_map (struct tar_stat_info *st, struct sp_array const *sp) st->sparse_map_avail = avail + 1; } -/* Scan the sparse file and create its map */ +/* Scan the sparse file byte-by-byte and create its map. */ static bool -sparse_scan_file (struct tar_sparse_file *file) +sparse_scan_file_raw (struct tar_sparse_file *file) { struct tar_stat_info *st = file->stat_info; int fd = file->fd; @@ -223,41 +221,38 @@ sparse_scan_file (struct tar_sparse_file *file) st->archive_file_size = 0; - if (ST_NBLOCKS (st->stat) == 0) - offset = st->stat.st_size; - else - { - if (!tar_sparse_scan (file, scan_begin, NULL)) - return false; - - while ((count = safe_read (fd, buffer, sizeof buffer)) != 0 - && count != SAFE_READ_ERROR) - { - /* Analyze the block. */ - if (zero_block_p (buffer, count)) - { - 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; - } + if (!tar_sparse_scan (file, scan_begin, NULL)) + return false; - offset += count; - } + while ((count = blocking_read (fd, buffer, sizeof buffer)) != 0 + && count != SAFE_READ_ERROR) + { + /* Analyze the block. */ + if (zero_block_p (buffer, count)) + { + 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; + } + + offset += count; } + /* save one more sparse segment of length 0 to indicate that + the file ends with a hole */ if (sp.numbytes == 0) sp.offset = offset; @@ -266,6 +261,114 @@ sparse_scan_file (struct tar_sparse_file *file) return tar_sparse_scan (file, scan_end, NULL); } +static bool +sparse_scan_file_wholesparse (struct tar_sparse_file *file) +{ + struct tar_stat_info *st = file->stat_info; + struct sp_array sp = {0, 0}; + + /* Note that this function is called only for truly sparse files of size >= 1 + block size (checked via ST_IS_SPARSE before). See the thread + http://www.mail-archive.com/bug-tar@gnu.org/msg04209.html for more info */ + if (ST_NBLOCKS (st->stat) == 0) + { + st->archive_file_size = 0; + sp.offset = st->stat.st_size; + sparse_add_map (st, &sp); + return true; + } + + return false; +} + +#ifdef SEEK_HOLE +/* Try to engage SEEK_HOLE/SEEK_DATA feature. */ +static bool +sparse_scan_file_seek (struct tar_sparse_file *file) +{ + struct tar_stat_info *st = file->stat_info; + int fd = file->fd; + struct sp_array sp = {0, 0}; + off_t offset = 0; + off_t data_offset; + off_t hole_offset; + + st->archive_file_size = 0; + + for (;;) + { + /* locate first chunk of data */ + data_offset = lseek (fd, offset, SEEK_DATA); + + if (data_offset == (off_t)-1) + /* ENXIO == EOF; error otherwise */ + { + if (errno == ENXIO) + { + /* file ends with hole, add one more empty chunk of data */ + sp.numbytes = 0; + sp.offset = st->stat.st_size; + sparse_add_map (st, &sp); + return true; + } + return false; + } + + hole_offset = lseek (fd, data_offset, SEEK_HOLE); + + /* according to specs, if FS does not fully support + SEEK_DATA/SEEK_HOLE it may just implement kind of "wrapper" around + classic lseek() call. We must detect it here and try to use other + hole-detection methods. */ + if (offset == 0 /* first loop */ + && data_offset == 0 + && hole_offset == st->stat.st_size) + { + lseek (fd, 0, SEEK_SET); + return false; + } + + sp.offset = data_offset; + sp.numbytes = hole_offset - data_offset; + sparse_add_map (st, &sp); + + st->archive_file_size += sp.numbytes; + offset = hole_offset; + } + + return true; +} +#endif + +static bool +sparse_scan_file (struct tar_sparse_file *file) +{ + /* always check for completely sparse files */ + if (sparse_scan_file_wholesparse (file)) + return true; + + switch (hole_detection) + { + case HOLE_DETECTION_DEFAULT: + case HOLE_DETECTION_SEEK: +#ifdef SEEK_HOLE + if (sparse_scan_file_seek (file)) + return true; +#else + if (hole_detection == HOLE_DETECTION_SEEK) + WARN((0, 0, + _("\"seek\" hole detection is not supported, using \"raw\"."))); + /* fall back to "raw" for this and all other files */ + hole_detection = HOLE_DETECTION_RAW; +#endif + case HOLE_DETECTION_RAW: + if (sparse_scan_file_raw (file)) + return true; + } + + return false; +} + static struct tar_sparse_optab const oldgnu_optab; static struct tar_sparse_optab const star_optab; static struct tar_sparse_optab const pax_optab; @@ -360,7 +463,7 @@ sparse_extract_region (struct tar_sparse_file *file, size_t i) return false; } set_next_block_after (blk); - count = full_write (file->fd, blk->buffer, wrbytes); + count = blocking_write (file->fd, blk->buffer, wrbytes); write_size -= count; file->dumped_size += count; mv_size_left (file->stat_info->archive_file_size - file->dumped_size); @@ -591,18 +694,18 @@ sparse_diff_file (int fd, struct tar_stat_info *st) /* 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 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. + actual file size is stored in 'realsize' member of oldgnu_header. - The map of the file is stored in a list of `struct sparse'. + 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' + 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' + (SPARSES_IN_SPARSE_HEADER). If more structs follow, 'isextended' field of the extended header is set and next next extension header follows, etc... */ @@ -629,8 +732,8 @@ oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s) return add_finish; sp.offset = OFF_FROM_HEADER (s->offset); sp.numbytes = OFF_FROM_HEADER (s->numbytes); - if (sp.offset < 0 - || sp.offset + sp.numbytes < 0 + 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; @@ -644,10 +747,10 @@ 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 = 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 = - OFF_FROM_HEADER (current_header->oldgnu_header.realsize); - return true; + file->stat_info->stat.st_size = max (0, realsize); + return 0 <= realsize; } /* Convert old GNU format sparse data to internal representation */ @@ -768,10 +871,10 @@ 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 = 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 = - OFF_FROM_HEADER (current_header->star_in_header.realsize); - return true; + file->stat_info->stat.st_size = max (0, realsize); + return 0 <= realsize; } /* Convert STAR format sparse data to internal representation */ @@ -811,6 +914,7 @@ star_get_sparse_info (struct tar_sparse_file *file) 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; } if (rc == add_fail) @@ -919,6 +1023,18 @@ pax_sparse_member_p (struct tar_sparse_file *file) || file->stat_info->sparse_major > 0; } +/* Start a header that uses the effective (shrunken) file size. */ +static union block * +pax_start_header (struct tar_stat_info *st) +{ + off_t realsize = st->stat.st_size; + union block *blk; + st->stat.st_size = st->archive_file_size; + blk = start_header (st); + st->stat.st_size = realsize; + return blk; +} + static bool pax_dump_header_0 (struct tar_sparse_file *file) { @@ -968,9 +1084,7 @@ pax_dump_header_0 (struct tar_sparse_file *file) return false; } } - blk = start_header (file->stat_info); - /* Store the effective (shrunken) file size */ - OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size); + blk = pax_start_header (file->stat_info); finish_header (file->stat_info, blk, block_ordinal); if (save_file_name) { @@ -1029,12 +1143,13 @@ pax_dump_header_1 (struct tar_sparse_file *file) 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->file_name = + xheader_format_name (file->stat_info, "%d/GNUSparseFile.%p/%f", 0); + /* Make sure the created header name is shorter than NAME_FIELD_SIZE: */ + if (strlen (file->stat_info->file_name) > NAME_FIELD_SIZE) + file->stat_info->file_name[NAME_FIELD_SIZE] = 0; - blk = start_header (file->stat_info); - /* Store the effective (shrunken) file size */ - OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size); + blk = pax_start_header (file->stat_info); finish_header (file->stat_info, blk, block_ordinal); free (file->stat_info->file_name); file->stat_info->file_name = save_file_name; @@ -1077,6 +1192,7 @@ decode_num (uintmax_t *num, char const *arg, uintmax_t maxval) if (!ISDIGIT (*arg)) return false; + errno = 0; u = strtoumax (arg, &arg_lim, 10); if (! (u <= maxval && errno != ERANGE) || *arg_lim)