X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fsparse.c;h=a661ab18d3a47b3ed6cf5446130fc1dc957d2a76;hb=d46025b23c5bbe31902ba4e902312ce8fe05ed63;hp=4186254549cccc40d244c14e07e7b5cca838bab9;hpb=18ef8acac57d9af27d42b11925c8767470976be9;p=chaz%2Ftar diff --git a/src/sparse.c b/src/sparse.c index 4186254..a661ab1 100644 --- a/src/sparse.c +++ b/src/sparse.c @@ -130,8 +130,8 @@ zero_block_p (char *buffer, size_t size) { while (size--) if (*buffer++) - return 0; - return 1; + return false; + return true; } #define clear_block(p) memset (p, 0, BLOCKSIZE); @@ -205,21 +205,21 @@ sparse_scan_file (struct tar_sparse_file *file) } if (sp.numbytes == 0) - { - sp.offset = offset - 1; - sp.numbytes = 1; - } + sp.offset = offset; + sparse_add_map (file, &sp); file->stat_info->archive_file_size += count; return tar_sparse_scan (file, scan_end, NULL); } static struct tar_sparse_optab oldgnu_optab; +static struct tar_sparse_optab star_optab; +static struct tar_sparse_optab pax_optab; static bool sparse_select_optab (struct tar_sparse_file *file) { - switch (archive_format) + switch (current_format == DEFAULT_FORMAT ? archive_format : current_format) { case V7_FORMAT: case USTAR_FORMAT: @@ -231,12 +231,15 @@ sparse_select_optab (struct tar_sparse_file *file) break; case POSIX_FORMAT: - case STAR_FORMAT: - /* FIXME: Add methods */ - return false; + file->optab = &pax_optab; + break; - default: + case STAR_FORMAT: + file->optab = &star_optab; break; + + default: + return false; } return true; } @@ -251,7 +254,7 @@ sparse_dump_region (struct tar_sparse_file *file, size_t index) SEEK_SET)) return false; - do + while (bytes_left > 0) { size_t bufsize = (bytes_left > BLOCKSIZE) ? BLOCKSIZE : bytes_left; off_t bytes_read; @@ -273,7 +276,7 @@ sparse_dump_region (struct tar_sparse_file *file, size_t index) file->dumped_size += bytes_read; set_next_block_after (blk); } - while (bytes_left > 0); + return true; } @@ -285,8 +288,16 @@ sparse_extract_region (struct tar_sparse_file *file, size_t index) if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset, SEEK_SET)) return false; + write_size = file->stat_info->sparse_map[index].numbytes; - while (write_size > 0) + + if (write_size == 0) + { + /* Last block of the file is a hole */ + if (sys_truncate (file->fd)) + truncate_warn (file->stat_info->orig_file_name); + } + else while (write_size > 0) { size_t count; size_t wrbytes = (write_size > BLOCKSIZE) ? BLOCKSIZE : write_size; @@ -374,6 +385,117 @@ sparse_extract_file (int fd, struct tar_stat_info *stat, off_t *size) return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short; } + +static char diff_buffer[BLOCKSIZE]; + +static bool +check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end) +{ + if (!lseek_or_error (file, beg, SEEK_SET)) + return false; + + while (beg < end) + { + size_t bytes_read; + size_t rdsize = end - beg; + + if (rdsize > BLOCKSIZE) + rdsize = BLOCKSIZE; + clear_block (diff_buffer); + bytes_read = safe_read (file->fd, diff_buffer, rdsize); + if (bytes_read < 0) + { + read_diag_details (file->stat_info->orig_file_name, + beg, + rdsize); + return false; + } + if (!zero_block_p (diff_buffer, bytes_read)) + { + report_difference (file->stat_info, + _("File fragment at %lu is not a hole"), beg); + return false; + } + + beg += bytes_read; + } + return true; +} + +static bool +check_data_region (struct tar_sparse_file *file, size_t index) +{ + size_t size_left; + + if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset, + SEEK_SET)) + return false; + size_left = file->stat_info->sparse_map[index].numbytes; + while (size_left > 0) + { + size_t bytes_read; + size_t rdsize = (size_left > BLOCKSIZE) ? BLOCKSIZE : size_left; + + union block *blk = find_next_block (); + if (!blk) + { + ERROR ((0, 0, _("Unexpected EOF in archive"))); + return false; + } + set_next_block_after (blk); + bytes_read = safe_read (file->fd, diff_buffer, rdsize); + if (bytes_read < 0) + { + read_diag_details (file->stat_info->orig_file_name, + file->stat_info->sparse_map[index].offset + + file->stat_info->sparse_map[index].numbytes + - size_left, + rdsize); + return false; + } + file->dumped_size += bytes_read; + size_left -= bytes_read; + if (memcmp (blk->buffer, diff_buffer, rdsize)) + { + report_difference (file->stat_info, _("Contents differ")); + return false; + } + } + return true; +} + +bool +sparse_diff_file (int fd, struct tar_stat_info *stat) +{ + bool rc = true; + struct tar_sparse_file file; + size_t i; + off_t offset = 0; + + file.stat_info = stat; + file.fd = fd; + + if (!sparse_select_optab (&file) + || !tar_sparse_init (&file)) + return dump_status_not_implemented; + + rc = tar_sparse_decode_header (&file); + for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++) + { + rc = check_sparse_region (&file, + offset, file.stat_info->sparse_map[i].offset) + && check_data_region (&file, i); + offset = file.stat_info->sparse_map[i].offset + + file.stat_info->sparse_map[i].numbytes; + } + + if (!rc) + skip_file (file.stat_info->archive_file_size - file.dumped_size); + + tar_sparse_done (&file); + return rc; +} + /* Old GNU Format. The sparse file information is stored in the oldgnu_header in the following manner: @@ -529,3 +651,128 @@ static struct tar_sparse_optab oldgnu_optab = { sparse_dump_region, sparse_extract_region, }; + + +/* Star */ + +/* Convert STAR format sparse data to internal representation + FIXME: Clubbers current_header! */ +static bool +star_get_sparse_info (struct tar_sparse_file *file) +{ + size_t i; + union block *h = current_header; + int ext_p; + static enum oldgnu_add_status rc; + + /* FIXME: note this! st_size was initialized from the header + which actually contains archived size. The following fixes it */ + 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); + + file->stat_info->sparse_map_size = 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; + } + else + ext_p = 1; + + for (; rc == add_ok && ext_p; ext_p = h->star_ext_header.isextended) + { + h = find_next_block (); + if (!h) + { + ERROR ((0, 0, _("Unexpected EOF in archive"))); + return false; + } + 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]); + } + + if (rc == add_fail) + { + ERROR ((0, 0, _("%s: invalid sparse archive member"), + file->stat_info->orig_file_name)); + return false; + } + return true; +} + + +static struct tar_sparse_optab star_optab = { + NULL, /* No init function */ + NULL, /* No done function */ + NULL, + star_get_sparse_info, + NULL, /* No scan_block function */ + NULL, /* No dump region function */ + sparse_extract_region, +}; + + +/* GNU PAX sparse file format. 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 +*/ + +static bool +pax_dump_header (struct tar_sparse_file *file) +{ + off_t block_ordinal = current_block_ordinal (); + union block *blk; + size_t i; + + /* Store the real file size */ + xheader_store ("GNU.sparse.size", file->stat_info, NULL); + xheader_store ("GNU.sparse.numblocks", file->stat_info, NULL); + for (i = 0; i < file->stat_info->sparse_map_avail; i++) + { + xheader_store ("GNU.sparse.offset", file->stat_info, &i); + xheader_store ("GNU.sparse.numbytes", file->stat_info, &i); + } + + blk = start_header (file->stat_info); + /* Store the effective (shrunken) file size */ + OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size); + finish_header (file->stat_info, blk, block_ordinal); + return true; +} + +static bool +pax_decode_header (struct tar_sparse_file *file) +{ + /* Restore actual size */ + size_t s = file->stat_info->archive_file_size; + file->stat_info->archive_file_size = file->stat_info->stat.st_size; + file->stat_info->stat.st_size = s; + return true; +} + +static struct tar_sparse_optab pax_optab = { + NULL, /* No init function */ + NULL, /* No done function */ + pax_dump_header, + pax_decode_header, + NULL, /* No scan_block function */ + sparse_dump_region, + sparse_extract_region, +}; +