X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fsparse.c;h=fb77aefdf483a9a2333d6711010663810020d67e;hb=120e96c480fbfc5fa31fba0f59ba1a45953b838e;hp=4f936dc6faed959aa93a20f6d74df4cd821543df;hpb=a8b2b68c33e6166c8035f094574fc0d1d3459cd8;p=chaz%2Ftar diff --git a/src/sparse.c b/src/sparse.c index 4f936dc..fb77aef 100644 --- a/src/sparse.c +++ b/src/sparse.c @@ -1,6 +1,6 @@ -/* Functions for dealing with sparse files +/* Functions for dealing with sparse files - Copyright (C) 2003 Free Software Foundation, Inc. + Copyright (C) 2003, 2004, 2005 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,9 +14,9 @@ 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., - 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "system.h" +#include #include #include "common.h" @@ -33,17 +33,22 @@ struct tar_sparse_optab { bool (*init) (struct tar_sparse_file *); bool (*done) (struct tar_sparse_file *); + bool (*sparse_member_p) (struct tar_sparse_file *); bool (*dump_header) (struct tar_sparse_file *); + bool (*fixup_header) (struct tar_sparse_file *); bool (*decode_header) (struct tar_sparse_file *); bool (*scan_block) (struct tar_sparse_file *, enum sparse_scan_state, void *); - bool (*dump_region) (struct tar_sparse_file *, size_t index); - bool (*extract_region) (struct tar_sparse_file *, size_t index); + bool (*dump_region) (struct tar_sparse_file *, size_t); + bool (*extract_region) (struct tar_sparse_file *, size_t); }; struct tar_sparse_file { int fd; /* File descriptor */ + bool seekable; /* Is fd seekable? */ + size_t offset; /* Current offset in fd if seekable==false. + Otherwise unused */ size_t dumped_size; /* Number of bytes actually written to the archive */ struct tar_stat_info *stat_info; /* Information about the file */ @@ -52,6 +57,47 @@ struct tar_sparse_file reqiure */ }; +/* Dump zeros to file->fd until offset is reached. It is used instead of + lseek if the output file is not seekable */ +static long +dump_zeros (struct tar_sparse_file *file, off_t offset) +{ + char buf[BLOCKSIZE]; + + if (offset - file->offset < 0) + { + errno = EINVAL; + return -1; + } + + memset (buf, 0, sizeof buf); + while (file->offset < offset) + { + size_t size = offset - file->offset; + size_t wrbytes; + + if (size > sizeof buf) + size = sizeof buf; + wrbytes = write (file->fd, buf, size); + if (wrbytes <= 0) + { + if (wrbytes == 0) + errno = EINVAL; + return -1; + } + file->offset += wrbytes; + } + return file->offset; +} + +static bool +tar_sparse_member_p (struct tar_sparse_file *file) +{ + if (file->optab->sparse_member_p) + return file->optab->sparse_member_p (file); + return false; +} + static bool tar_sparse_init (struct tar_sparse_file *file) { @@ -79,18 +125,18 @@ tar_sparse_scan (struct tar_sparse_file *file, enum sparse_scan_state state, } static bool -tar_sparse_dump_region (struct tar_sparse_file *file, size_t index) +tar_sparse_dump_region (struct tar_sparse_file *file, size_t i) { if (file->optab->dump_region) - return file->optab->dump_region (file, index); + return file->optab->dump_region (file, i); return false; } static bool -tar_sparse_extract_region (struct tar_sparse_file *file, size_t index) +tar_sparse_extract_region (struct tar_sparse_file *file, size_t i) { if (file->optab->extract_region) - return file->optab->extract_region (file, index); + return file->optab->extract_region (file, i); return false; } @@ -107,14 +153,29 @@ tar_sparse_decode_header (struct tar_sparse_file *file) { if (file->optab->decode_header) return file->optab->decode_header (file); - return false; + return true; +} + +static bool +tar_sparse_fixup_header (struct tar_sparse_file *file) +{ + if (file->optab->fixup_header) + return file->optab->fixup_header (file); + return true; } static bool -lseek_or_error (struct tar_sparse_file *file, off_t offset, int whence) +lseek_or_error (struct tar_sparse_file *file, off_t offset) { - if (lseek (file->fd, offset, whence) < 0) + off_t off; + + if (file->seekable) + off = lseek (file->fd, offset, SEEK_SET); + else + off = dump_zeros (file, offset); + + if (off < 0) { seek_diag_details (file->stat_info->orig_file_name, offset); return false; @@ -164,20 +225,21 @@ sparse_scan_file (struct tar_sparse_file *file) { static char buffer[BLOCKSIZE]; size_t count; - size_t offset = 0; + off_t offset = 0; struct sp_array sp = {0, 0}; - if (!lseek_or_error (file, 0, SEEK_SET)) + if (!lseek_or_error (file, 0)) return false; clear_block (buffer); - file->stat_info->sparse_map_size = 0; + file->stat_info->sparse_map_avail = 0; file->stat_info->archive_file_size = 0; - + if (!tar_sparse_scan (file, scan_begin, NULL)) return false; - while ((count = safe_read (file->fd, buffer, sizeof buffer)) > 0) + while ((count = safe_read (file->fd, buffer, sizeof buffer)) != 0 + && count != SAFE_READ_ERROR) { /* Analize the block */ if (zero_block_p (buffer, count)) @@ -199,27 +261,27 @@ sparse_scan_file (struct tar_sparse_file *file) if (!tar_sparse_scan (file, scan_block, buffer)) return false; } - + offset += count; clear_block (buffer); } - + 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,39 +293,41 @@ sparse_select_optab (struct tar_sparse_file *file) break; case POSIX_FORMAT: + file->optab = &pax_optab; + break; + case STAR_FORMAT: - /* FIXME: Add methods */ - return false; + file->optab = &star_optab; + break; default: - break; + return false; } return true; } static bool -sparse_dump_region (struct tar_sparse_file *file, size_t index) +sparse_dump_region (struct tar_sparse_file *file, size_t i) { union block *blk; - off_t bytes_left = file->stat_info->sparse_map[index].numbytes; - - if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset, - SEEK_SET)) + off_t bytes_left = file->stat_info->sparse_map[i].numbytes; + + if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset)) return false; - do + while (bytes_left > 0) { size_t bufsize = (bytes_left > BLOCKSIZE) ? BLOCKSIZE : bytes_left; - off_t bytes_read; - + size_t bytes_read; + blk = find_next_block (); memset (blk->buffer, 0, BLOCKSIZE); bytes_read = safe_read (file->fd, blk->buffer, bufsize); - if (bytes_read < 0) + if (bytes_read == SAFE_READ_ERROR) { read_diag_details (file->stat_info->orig_file_name, - file->stat_info->sparse_map[index].offset - + file->stat_info->sparse_map[index].numbytes + file->stat_info->sparse_map[i].offset + + file->stat_info->sparse_map[i].numbytes - bytes_left, bufsize); return false; @@ -273,20 +337,27 @@ 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; } static bool -sparse_extract_region (struct tar_sparse_file *file, size_t index) +sparse_extract_region (struct tar_sparse_file *file, size_t i) { size_t write_size; - - if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset, - SEEK_SET)) + + if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset)) return false; - write_size = file->stat_info->sparse_map[index].numbytes; - while (write_size > 0) + + write_size = file->stat_info->sparse_map[i].numbytes; + + if (write_size == 0) + { + /* Last block of the file is a hole */ + if (file->seekable && 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; @@ -300,6 +371,7 @@ sparse_extract_region (struct tar_sparse_file *file, size_t index) count = full_write (file->fd, blk->buffer, wrbytes); write_size -= count; file->dumped_size += count; + file->offset += count; if (count != wrbytes) { write_error_details (file->stat_info->orig_file_name, @@ -314,14 +386,16 @@ sparse_extract_region (struct tar_sparse_file *file, size_t index) /* Interface functions */ enum dump_status -sparse_dump_file (int fd, struct tar_stat_info *stat) +sparse_dump_file (int fd, struct tar_stat_info *st) { bool rc; struct tar_sparse_file file; - file.stat_info = stat; + file.stat_info = st; file.fd = fd; - + file.seekable = true; /* File *must* be seekable for dump to work */ + file.offset = 0; + if (!sparse_select_optab (&file) || !tar_sparse_init (&file)) return dump_status_not_implemented; @@ -346,23 +420,47 @@ sparse_dump_file (int fd, struct tar_stat_info *stat) /* Returns true if the file represented by stat is a sparse one */ bool -sparse_file_p (struct tar_stat_info *stat) +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) +{ + struct tar_sparse_file file; + + if (!sparse_select_optab (&file)) + return false; + file.stat_info = st; + return tar_sparse_member_p (&file); +} + +bool +sparse_fixup_header (struct tar_stat_info *st) { - return (ST_NBLOCKS (stat->stat) - < (stat->stat.st_size / ST_NBLOCKSIZE - + (stat->stat.st_size % ST_NBLOCKSIZE != 0))); + struct tar_sparse_file file; + + if (!sparse_select_optab (&file)) + return false; + file.stat_info = st; + return tar_sparse_fixup_header (&file); } enum dump_status -sparse_extract_file (int fd, struct tar_stat_info *stat, off_t *size) +sparse_extract_file (int fd, struct tar_stat_info *st, off_t *size) { bool rc = true; struct tar_sparse_file file; size_t i; - - file.stat_info = stat; - file.fd = fd; + file.stat_info = st; + file.fd = fd; + file.seekable = lseek (fd, 0, SEEK_SET) == 0; + file.offset = 0; + if (!sparse_select_optab (&file) || !tar_sparse_init (&file)) return dump_status_not_implemented; @@ -374,25 +472,43 @@ 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; } +enum dump_status +sparse_skip_file (struct tar_stat_info *st) +{ + bool rc = true; + struct tar_sparse_file file; + + file.stat_info = st; + file.fd = -1; + + if (!sparse_select_optab (&file) + || !tar_sparse_init (&file)) + return dump_status_not_implemented; + + rc = tar_sparse_decode_header (&file); + skip_file (file.stat_info->archive_file_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)) + if (!lseek_or_error (file, beg)) 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) + if (bytes_read == SAFE_READ_ERROR) { read_diag_details (file->stat_info->orig_file_name, beg, @@ -412,19 +528,18 @@ check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end) } static bool -check_data_region (struct tar_sparse_file *file, size_t index) +check_data_region (struct tar_sparse_file *file, size_t i) { size_t size_left; - - if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset, - SEEK_SET)) + + if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset)) return false; - size_left = file->stat_info->sparse_map[index].numbytes; + size_left = file->stat_info->sparse_map[i].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) { @@ -433,11 +548,11 @@ check_data_region (struct tar_sparse_file *file, size_t index) } set_next_block_after (blk); bytes_read = safe_read (file->fd, diff_buffer, rdsize); - if (bytes_read < 0) + if (bytes_read == SAFE_READ_ERROR) { read_diag_details (file->stat_info->orig_file_name, - file->stat_info->sparse_map[index].offset - + file->stat_info->sparse_map[index].numbytes + file->stat_info->sparse_map[i].offset + + file->stat_info->sparse_map[i].numbytes - size_left, rdsize); return false; @@ -454,14 +569,14 @@ check_data_region (struct tar_sparse_file *file, size_t index) } bool -sparse_diff_file (int fd, struct tar_stat_info *stat) +sparse_diff_file (int fd, struct tar_stat_info *st) { bool rc = true; struct tar_sparse_file file; size_t i; off_t offset = 0; - - file.stat_info = stat; + + file.stat_info = st; file.fd = fd; if (!sparse_select_optab (&file) @@ -478,21 +593,21 @@ sparse_diff_file (int fd, struct tar_stat_info *stat) + file.stat_info->sparse_map[i].numbytes; } - if (rc) + 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: 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 @@ -511,8 +626,14 @@ enum oldgnu_add_status add_fail }; +static bool +oldgnu_sparse_member_p (struct tar_sparse_file *file __attribute__ ((unused))) +{ + return current_header->header.typeflag == GNUTYPE_SPARSE; +} + /* Add a sparse item to the sparse file and its obstack */ -static enum oldgnu_add_status +static enum oldgnu_add_status oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s) { struct sp_array sp; @@ -530,8 +651,18 @@ oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s) return add_ok; } -/* Convert old GNU format sparse data to internal representation - FIXME: Clubbers current_header! */ +static bool +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 */ + 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; +} + +/* Convert old GNU format sparse data to internal representation */ static bool oldgnu_get_sparse_info (struct tar_sparse_file *file) { @@ -539,14 +670,8 @@ oldgnu_get_sparse_info (struct tar_sparse_file *file) 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->oldgnu_header.realsize); - - file->stat_info->sparse_map_size = 0; + + file->stat_info->sparse_map_avail = 0; for (i = 0; i < SPARSES_IN_OLDGNU_HEADER; i++) { rc = oldgnu_add_sparse (file, &h->oldgnu_header.sp[i]); @@ -597,7 +722,7 @@ oldgnu_dump_header (struct tar_sparse_file *file) off_t block_ordinal = current_block_ordinal (); union block *blk; size_t i; - + blk = start_header (file->stat_info); blk->header.typeflag = GNUTYPE_SPARSE; if (file->stat_info->sparse_map_avail > SPARSES_IN_OLDGNU_HEADER) @@ -614,7 +739,7 @@ oldgnu_dump_header (struct tar_sparse_file *file) SPARSES_IN_OLDGNU_HEADER); blk->oldgnu_header.isextended = i < file->stat_info->sparse_map_avail; finish_header (file->stat_info, blk, block_ordinal); - + while (i < file->stat_info->sparse_map_avail) { blk = find_next_block (); @@ -634,9 +759,146 @@ oldgnu_dump_header (struct tar_sparse_file *file) static struct tar_sparse_optab oldgnu_optab = { NULL, /* No init function */ NULL, /* No done function */ + oldgnu_sparse_member_p, oldgnu_dump_header, + oldgnu_fixup_header, oldgnu_get_sparse_info, NULL, /* No scan_block function */ sparse_dump_region, sparse_extract_region, }; + + +/* Star */ + +static bool +star_sparse_member_p (struct tar_sparse_file *file __attribute__ ((unused))) +{ + return current_header->header.typeflag == GNUTYPE_SPARSE; +} + +static bool +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 */ + 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; +} + +/* Convert STAR format sparse data to internal representation */ +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; + + file->stat_info->sparse_map_avail = 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 */ + star_sparse_member_p, + NULL, + star_fixup_header, + 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_sparse_member_p (struct tar_sparse_file *file) +{ + return file->stat_info->archive_file_size != file->stat_info->stat.st_size; +} + +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 struct tar_sparse_optab pax_optab = { + NULL, /* No init function */ + NULL, /* No done function */ + pax_sparse_member_p, + pax_dump_header, + NULL, /* No decode_header function */ + NULL, /* No fixup_header function */ + NULL, /* No scan_block function */ + sparse_dump_region, + sparse_extract_region, +}; +