struct fiemap *fm;
struct fiemap_extent *fe;
int64_t size;
- int count, do_fiemap;
+ int count, do_fiemap, iters;
int exit_sts = ARCHIVE_OK;
if (archive_entry_filetype(entry) != AE_IFREG
fm->fm_extent_count = count;
do_fiemap = 1;
size = archive_entry_size(entry);
- for (;;) {
+ for (iters = 0; ; ++iters) {
int i, r;
r = ioctl(*fd, FS_IOC_FIEMAP, fm);
* version(<2.6.28) cannot perfom FS_IOC_FIEMAP. */
goto exit_setup_sparse;
}
- if (fm->fm_mapped_extents == 0)
+ if (fm->fm_mapped_extents == 0) {
+ if (iters == 0) {
+ /* Fully sparse file; insert a zero-length "data" entry */
+ archive_entry_sparse_add_entry(entry, 0, 0);
+ }
break;
+ }
fe = fm->fm_extents;
for (i = 0; i < (int)fm->fm_mapped_extents; i++, fe++) {
if (!(fe->fe_flags & FIEMAP_EXTENT_UNWRITTEN)) {
off_t initial_off; /* FreeBSD/Solaris only, so off_t okay here */
off_t off_s, off_e; /* FreeBSD/Solaris only, so off_t okay here */
int exit_sts = ARCHIVE_OK;
+ int check_fully_sparse = 0;
if (archive_entry_filetype(entry) != AE_IFREG
|| archive_entry_size(entry) <= 0
while (off_s < size) {
off_s = lseek(*fd, off_s, SEEK_DATA);
if (off_s == (off_t)-1) {
- if (errno == ENXIO)
- break;/* no more hole */
+ if (errno == ENXIO) {
+ /* no more hole */
+ if (archive_entry_sparse_count(entry) == 0) {
+ /* Potentially a fully-sparse file. */
+ check_fully_sparse = 1;
+ }
+ break;
+ }
archive_set_error(&a->archive, errno,
"lseek(SEEK_HOLE) failed");
exit_sts = ARCHIVE_FAILED;
off_e - off_s);
off_s = off_e;
}
+
+ if (check_fully_sparse) {
+ if (lseek(*fd, 0, SEEK_HOLE) == 0 &&
+ lseek(*fd, 0, SEEK_END) == size) {
+ /* Fully sparse file; insert a zero-length "data" entry */
+ archive_entry_sparse_add_entry(entry, 0, 0);
+ }
+ }
exit_setup_sparse:
lseek(*fd, initial_off, SEEK_SET);
return (exit_sts);
int r;
ssize_t bytes;
size_t buffbytes;
+ int empty_sparse_region = 0;
archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA,
"archive_read_data_block");
if ((int64_t)buffbytes > t->current_sparse->length)
buffbytes = t->current_sparse->length;
+ if (t->current_sparse->length == 0)
+ empty_sparse_region = 1;
+
/*
* Skip hole.
* TODO: Should we consider t->current_filesystem->xfer_align?
}
} else
bytes = 0;
- if (bytes == 0) {
+ /*
+ * Return an EOF unless we've read a leading empty sparse region, which
+ * is used to represent fully-sparse files.
+ *
+ * TODO: it is not technically necessary to check for entry_tota == 0,
+ * but this simplifies some unit tests that expect to only read data
+ * regions with length > 0. Consider fixing these tests (test_sparse_basic).
+ */
+ if (bytes == 0 && !(empty_sparse_region && t->entry_total == 0)) {
/* Get EOF */
t->entry_eof = 1;
r = ARCHIVE_EOF;
assertEqualInt(ARCHIVE_OK, archive_read_free(a));
free(cwd);
}
+
+DEFINE_TEST(test_fully_sparse_files)
+{
+ char *cwd;
+ struct archive *a;
+
+ const struct sparse sparse_file[] = {
+ { HOLE, 409600 }, { END, 0 }
+ };
+ /* Check if the filesystem where CWD on can
+ * report the number of the holes of a sparse file. */
+#ifdef PATH_MAX
+ cwd = getcwd(NULL, PATH_MAX);/* Solaris getcwd needs the size. */
+#else
+ cwd = getcwd(NULL, 0);
+#endif
+ if (!assert(cwd != NULL))
+ return;
+ if (!is_sparse_supported(cwd)) {
+ free(cwd);
+ skipping("This filesystem or platform do not support "
+ "the reporting of the holes of a sparse file through "
+ "API such as lseek(HOLE)");
+ return;
+ }
+
+ assert((a = archive_read_disk_new()) != NULL);
+
+ /* Fully sparse files are encoded with a zero-length "data" block. */
+ verify_sparse_file(a, "file0", sparse_file, 1, 1);
+
+ assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+ free(cwd);
+}