]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Determine sparse files through API such as lseek(HOLE).
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Tue, 2 Feb 2010 11:09:17 +0000 (06:09 -0500)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Tue, 2 Feb 2010 11:09:17 +0000 (06:09 -0500)
SVN-Revision: 1856

CMakeLists.txt
Makefile.am
build/cmake/config.h.in
configure.ac
libarchive/CMakeLists.txt
libarchive/archive_read_disk_entry_from_file.c
libarchive/archive_read_support_format_tar.c
libarchive/test/CMakeLists.txt
libarchive/test/test_sparse_basic.c [new file with mode: 0644]

index f844e7eb1961478399b8334d8b81f1399a18b6b6..ea1272cd2f97ce555fe28ac30935348fb051172c 100644 (file)
@@ -224,6 +224,7 @@ LA_CHECK_INCLUDE_FILE("inttypes.h" HAVE_INTTYPES_H)
 LA_CHECK_INCLUDE_FILE("io.h" HAVE_IO_H)
 LA_CHECK_INCLUDE_FILE("langinfo.h" HAVE_LANGINFO_H)
 LA_CHECK_INCLUDE_FILE("limits.h" HAVE_LIMITS_H)
+LA_CHECK_INCLUDE_FILE("linux/fiemap.h" HAVE_LINUX_FIEMAP_H)
 LA_CHECK_INCLUDE_FILE("linux/fs.h" HAVE_LINUX_FS_H)
 LA_CHECK_INCLUDE_FILE("locale.h" HAVE_LOCALE_H)
 LA_CHECK_INCLUDE_FILE("memory.h" HAVE_MEMORY_H)
@@ -249,6 +250,7 @@ LA_CHECK_INCLUDE_FILE("sys/select.h" HAVE_SYS_SELECT_H)
 LA_CHECK_INCLUDE_FILE("sys/stat.h" HAVE_SYS_STAT_H)
 LA_CHECK_INCLUDE_FILE("sys/time.h" HAVE_SYS_TIME_H)
 LA_CHECK_INCLUDE_FILE("sys/utime.h" HAVE_SYS_UTIME_H)
+LA_CHECK_INCLUDE_FILE("sys/utsname.h" HAVE_SYS_UTSNAME_H)
 LA_CHECK_INCLUDE_FILE("sys/wait.h" HAVE_SYS_WAIT_H)
 LA_CHECK_INCLUDE_FILE("time.h" HAVE_TIME_H)
 LA_CHECK_INCLUDE_FILE("unistd.h" HAVE_UNISTD_H)
@@ -256,6 +258,7 @@ LA_CHECK_INCLUDE_FILE("utime.h" HAVE_UTIME_H)
 LA_CHECK_INCLUDE_FILE("wchar.h" HAVE_WCHAR_H)
 LA_CHECK_INCLUDE_FILE("wctype.h" HAVE_WCTYPE_H)
 LA_CHECK_INCLUDE_FILE("windows.h" HAVE_WINDOWS_H)
+LA_CHECK_INCLUDE_FILE("winioctl.h" HAVE_WINIOCTL_H)
 
 
 #
index 88cea157062614f98da69ae61efecfdfee0fdb6f..a2268f75b31ab11e8876d688543d1fe0dbd396e4 100644 (file)
@@ -99,6 +99,7 @@ libarchive_la_SOURCES=                                                \
        libarchive/archive_entry_copy_stat.c                    \
        libarchive/archive_entry_link_resolver.c                \
        libarchive/archive_entry_private.h                      \
+       libarchive/archive_entry_sparse.c                       \
        libarchive/archive_entry_stat.c                         \
        libarchive/archive_entry_strmode.c                      \
        libarchive/archive_entry_xattr.c                        \
@@ -294,6 +295,7 @@ libarchive_test_SOURCES=                                    \
        libarchive/test/test_read_position.c                    \
        libarchive/test/test_read_truncated.c                   \
        libarchive/test/test_read_uu.c                          \
+       libarchive/test/test_sparse_basic.c                     \
        libarchive/test/test_tar_filenames.c                    \
        libarchive/test/test_tar_large.c                        \
        libarchive/test/test_ustar_filenames.c                  \
index dadda86059db5e540b5d91235d4e8e32ddea209f..b9ff03fa7ae16a2aa307e19ffa516cc1ba84135e 100644 (file)
 /* Define to 1 if you have the link() function. */
 #cmakedefine HAVE_LINK 1
 
+/* Define to 1 if you have the <linux/fiemap.h> header file. */
+#cmakedefine HAVE_LINUX_FIEMAP_H 1
+
 /* Define to 1 if you have the <linux/fs.h> header file. */
 #cmakedefine HAVE_LINUX_FS_H 1
 
 /* Define to 1 if you have the <sys/utime.h> header file. */
 #cmakedefine HAVE_SYS_UTIME_H 1
 
+/* Define to 1 if you have the <sys/utsname.h> header file. */
+#cmakedefine HAVE_SYS_UTSNAME_H 1
+
 /* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
 #cmakedefine HAVE_SYS_WAIT_H 1
 
 /* Define to 1 if you have the <windows.h> header file. */
 #cmakedefine HAVE_WINDOWS_H 1
 
+/* Define to 1 if you have the <windioctl.h> header file. */
+#cmakedefine HAVE_WINIOCTL_H 1
+
 /* Define to 1 if you have _CrtSetReportMode in <crtdbg.h>  */
 #cmakedefine HAVE__CrtSetReportMode 1
 
    #define below would cause a syntax error. */
 #cmakedefine _UINT64_T
 
+/* Define for Windows to use Windows 2000+ APIs. */
+#cmakedefine _WIN32_WINNT ${_WIN32_WINNT}
+#cmakedefine WINVER ${WINVER}
+
 /* Define to empty if `const' does not conform to ANSI C. */
 #cmakedefine const ${const}
 
index e4beae79e82e8a271cd88ee07ba726e07d7fd5b8..513c46aaf3754fca0660e6a127a17e0ee6f986ac 100644 (file)
@@ -194,12 +194,14 @@ AC_HEADER_DIRENT
 AC_HEADER_SYS_WAIT
 AC_CHECK_HEADERS([acl/libacl.h attr/xattr.h copyfile.h ctype.h errno.h])
 AC_CHECK_HEADERS([ext2fs/ext2_fs.h fcntl.h grp.h])
-AC_CHECK_HEADERS([inttypes.h io.h langinfo.h limits.h linux/fs.h])
+AC_CHECK_HEADERS([inttypes.h io.h langinfo.h limits.h])
+AC_CHECK_HEADERS([linux/fiemap.h linux/fs.h])
 AC_CHECK_HEADERS([locale.h paths.h poll.h pwd.h regex.h signal.h stdarg.h])
 AC_CHECK_HEADERS([stdint.h stdlib.h string.h])
 AC_CHECK_HEADERS([sys/acl.h sys/cdefs.h sys/extattr.h sys/ioctl.h sys/mkdev.h])
 AC_CHECK_HEADERS([sys/param.h sys/poll.h sys/select.h sys/time.h sys/utime.h])
-AC_CHECK_HEADERS([time.h unistd.h utime.h wchar.h wctype.h windows.h])
+AC_CHECK_HEADERS([sys/utsname.h time.h unistd.h utime.h wchar.h wctype.h])
+AC_CHECK_HEADERS([windows.h winioctl])
 
 # Checks for libraries.
 AC_ARG_WITH([zlib],
index 459a5ddbb903c62093b0f84502778c96abfd6799..0ca57e616124974d8f41f9d19f163f9e826b7393 100644 (file)
@@ -20,6 +20,7 @@ SET(libarchive_SOURCES
   archive_entry_copy_stat.c
   archive_entry_link_resolver.c
   archive_entry_private.h
+  archive_entry_sparse.c
   archive_entry_stat.c
   archive_entry_strmode.c
   archive_entry_xattr.c
index de218c50ac9555cbbea164702670b776085528d0..054a89384fb07fa7743f86af0b2b8c9e450c2aaf 100644 (file)
@@ -1,5 +1,6 @@
 /*-
  * Copyright (c) 2003-2009 Tim Kientzle
+ * Copyright (c) 2010 Michihiro NAKAJIMA
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -36,6 +37,9 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_read_disk_entry_from_file.c 2010
 #ifdef HAVE_SYS_EXTATTR_H
 #include <sys/extattr.h>
 #endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
 #ifdef HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
@@ -54,11 +58,23 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_read_disk_entry_from_file.c 2010
 #ifdef HAVE_ERRNO_H
 #include <errno.h>
 #endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
 #ifdef HAVE_LIMITS_H
 #include <limits.h>
 #endif
-#ifdef HAVE_WINDOWS_H
-#include <windows.h>
+#ifdef HAVE_LINUX_FIEMAP_H
+#include <linux/fiemap.h>
+#endif
+#ifdef HAVE_LINUX_FS_H
+#include <linux/fs.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_WINIOCTL_H
+#include <winioctl.h>
 #endif
 
 #include "archive.h"
@@ -80,6 +96,8 @@ static int setup_acls_posix1e(struct archive_read_disk *,
     struct archive_entry *, int fd);
 static int setup_xattrs(struct archive_read_disk *,
     struct archive_entry *, int fd);
+static int setup_sparse(struct archive_read_disk *,
+    struct archive_entry *, int fd);
 
 int
 archive_read_disk_entry_from_file(struct archive *_a,
@@ -179,6 +197,12 @@ archive_read_disk_entry_from_file(struct archive *_a,
        r1 = setup_xattrs(a, entry, fd);
        if (r1 < r)
                r = r1;
+       if (S_ISREG(st->st_mode) && archive_entry_size(entry) > 0 &&
+           archive_entry_hardlink(entry) == NULL) {
+               r1 = setup_sparse(a, entry, fd);
+               if (r1 < r)
+                       r = r1;
+       }
        /* If we opened the file earlier in this function, close it. */
        if (initial_fd != fd)
                close(fd);
@@ -561,3 +585,291 @@ setup_xattrs(struct archive_read_disk *a,
 }
 
 #endif
+
+#if defined(HAVE_LINUX_FIEMAP_H)
+
+/*
+ * Linux sparse interface.
+ */
+
+static int
+setup_sparse(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       char buff[4096];
+       struct fiemap *fm;
+       struct fiemap_extent *fe;
+       int64_t size;
+       int count, do_fiemap;
+       int initial_fd = fd;
+
+       if (fd < 0) {
+               const char *path;
+
+               path = archive_entry_sourcepath(entry);
+               if (path == NULL)
+                       path = archive_entry_pathname(entry);
+               fd = open(path, O_RDONLY | O_NONBLOCK);
+               if (fd < 0) {
+                       archive_set_error(&a->archive, errno,
+                           "Can't open `%s'", path);
+                       return (ARCHIVE_FAILED);
+               }
+       }
+
+       count = (sizeof(buff) - sizeof(*fm))/sizeof(*fe);
+       fm = (struct fiemap *)buff;
+       fm->fm_start = 0;
+       fm->fm_length = ~0ULL;;
+       fm->fm_flags = FIEMAP_FLAG_SYNC;
+       fm->fm_extent_count = count;
+       do_fiemap = 1;
+       size = archive_entry_size(entry);
+       for (;;) {
+               int i, r;
+
+               r = ioctl(fd, FS_IOC_FIEMAP, fm); 
+               if (r < 0) {
+                       archive_set_error(&a->archive, errno, "FIEMAP failed");
+                       if (initial_fd != fd)
+                               close(fd);
+                       return (ARCHIVE_FAILED);
+               }
+               if (fm->fm_mapped_extents == 0)
+                       break;
+               fe = fm->fm_extents;
+               for (i = 0; i < fm->fm_mapped_extents; i++, fe++) {
+                       if (!(fe->fe_flags & FIEMAP_EXTENT_UNWRITTEN)) {
+                               /* The fe_length of the last block does not
+                                * adjust itself to its size files. */
+                               int64_t length = fe->fe_length;
+                               if (fe->fe_logical + length > size)
+                                       length -= fe->fe_logical + length - size;
+                               if (length > 0)
+                                       archive_entry_sparse_add_entry(entry,
+                                           fe->fe_logical, length);
+                       }
+                       if (fe->fe_flags & FIEMAP_EXTENT_LAST)
+                               do_fiemap = 0;
+               }
+               if (do_fiemap) {
+                       fe = fm->fm_extents + fm->fm_mapped_extents -1;
+                       fm->fm_start = fe->fe_logical + fe->fe_length;
+               } else
+                       break;
+       }
+       if (initial_fd != fd)
+               close(fd);
+       return (ARCHIVE_OK);
+}
+
+#elif defined(SEEK_HOLE) && defined(SEEK_DATA) && defined(_PC_MIN_HOLE_SIZE)
+
+/*
+ * FreeBSD and Solaris sparse interface.
+ */
+
+static int
+setup_sparse(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       int64_t size;
+       int initial_fd = fd;
+       off_t initial_off;
+       off_t off_s, off_e;
+       int exit_sts = ARCHIVE_OK;
+
+       /* Does filesystem support the reporting of hole ? */
+       if (fd >= 0) {
+               if (fpathconf(fd, _PC_MIN_HOLE_SIZE) <= 0)
+                       return (ARCHIVE_OK);
+               initial_off = lseek(fd, 0, SEEK_CUR);
+               if (initial_off != 0)
+                       lseek(fd, 0, SEEK_SET);
+       } else {
+               const char *path;
+
+               path = archive_entry_sourcepath(entry);
+               if (path == NULL)
+                       path = archive_entry_pathname(entry);
+               if (pathconf(path, _PC_MIN_HOLE_SIZE) <= 0)
+                       return (ARCHIVE_OK);
+               fd = open(path, O_RDONLY | O_NONBLOCK);
+               if (fd < 0) {
+                       archive_set_error(&a->archive, errno,
+                           "Can't open `%s'", path);
+                       return (ARCHIVE_FAILED);
+               }
+               initial_off = 0;
+       }
+
+       off_s = 0;
+       size = archive_entry_size(entry);
+       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 */
+                       archive_set_error(&a->archive, errno,
+                           "lseek(SEEK_HOLE) failed");
+                       exit_sts = ARCHIVE_FAILED;
+                       goto exit_setup_sparse;
+               }
+               off_e = lseek(fd, off_s, SEEK_HOLE);
+               if (off_s == (off_t)-1) {
+                       if (errno == ENXIO) {
+                               off_e = lseek(fd, 0, SEEK_END);
+                               if (off_e != (off_t)-1)
+                                       break;/* no more data */
+                       }
+                       archive_set_error(&a->archive, errno,
+                           "lseek(SEEK_DATA) failed");
+                       exit_sts = ARCHIVE_FAILED;
+                       goto exit_setup_sparse;
+               }
+               archive_entry_sparse_add_entry(entry, off_s,
+                       off_e - off_s);
+               off_s = off_e;
+       }
+exit_setup_sparse:
+       if (initial_fd != fd)
+               close(fd);
+       else
+               lseek(fd, initial_off, SEEK_SET);
+       return (exit_sts);
+}
+
+#elif defined(_WIN32) && !defined(__CYGWIN__)
+
+/*
+ * Windows sparse interface.
+ */
+
+static int
+setup_sparse(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       HANDLE handle;
+       BY_HANDLE_FILE_INFORMATION info;
+       FILE_ALLOCATED_RANGE_BUFFER range, *outranges = NULL;
+       size_t outranges_size;
+       LARGE_INTEGER fsize;
+       int initial_fd = fd;
+       int exit_sts = ARCHIVE_OK;
+
+       if (fd < 0) {
+               const char *path;
+
+               path = archive_entry_sourcepath(entry);
+               if (path == NULL)
+                       path = archive_entry_pathname(entry);
+               fd = open(path, O_RDONLY | O_BINARY);
+               if (fd < 0) {
+                       archive_set_error(&a->archive, errno,
+                           "Can't open `%s'", path);
+                       return (ARCHIVE_FAILED);
+               }
+       }
+       handle = (HANDLE)_get_osfhandle(fd);
+       ZeroMemory(&info, sizeof(info));
+       if (!GetFileInformationByHandle (handle, &info)) {
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                       "GetFileInformationByHandle Failed: %lu",
+                   GetLastError());
+               exit_sts = ARCHIVE_FAILED;
+               goto exit_setup_sparse;
+       }
+       if ((info.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) == 0)
+               goto exit_setup_sparse;/* Not sparse file */
+
+       GetFileSizeEx(handle, &fsize);
+       range.FileOffset.QuadPart = 0;
+       range.Length.QuadPart = fsize.QuadPart;
+       outranges_size = 2048;
+       outranges = (FILE_ALLOCATED_RANGE_BUFFER *)malloc(outranges_size);
+       if (outranges == NULL) {
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                       "Couldn't allocate memory");
+               exit_sts = ARCHIVE_FATAL;
+               goto exit_setup_sparse;
+       }
+
+       for (;;) {
+               DWORD retbytes;
+               BOOL ret;
+
+               for (;;) {
+                       ret = DeviceIoControl(handle,
+                           FSCTL_QUERY_ALLOCATED_RANGES,
+                           &range, sizeof(range), outranges,
+                           outranges_size, &retbytes, NULL);
+                       if (ret == 0 && GetLastError() == ERROR_MORE_DATA) {
+                               free(outranges);
+                               outranges_size *= 2;
+                               outranges = (FILE_ALLOCATED_RANGE_BUFFER *)
+                                   malloc(outranges_size);
+                               if (outranges == NULL) {
+                                       archive_set_error(&a->archive,
+                                           ARCHIVE_ERRNO_MISC,
+                                           "Couldn't allocate memory");
+                                       exit_sts = ARCHIVE_FATAL;
+                                       goto exit_setup_sparse;
+                               }
+                               continue;
+                       } else
+                               break;
+               }
+               if (ret != 0) {
+                       if (retbytes > 0) {
+                               DWORD i, n;
+
+                               n = retbytes / sizeof(outranges[0]);
+                               for (i = 0; i < n; i++)
+                                       archive_entry_sparse_add_entry(entry,
+                                           outranges[i].FileOffset.QuadPart,
+                                               outranges[i].Length.QuadPart);
+                               range.FileOffset.QuadPart =
+                                   outranges[n-1].FileOffset.QuadPart
+                                   + outranges[n-1].Length.QuadPart;
+                               range.Length.QuadPart =
+                                   fsize.QuadPart - range.FileOffset.QuadPart;
+                               if (range.Length.QuadPart > 0)
+                                       continue;
+                       } else {
+                               /* The remaining data is hole. */
+                               archive_entry_sparse_add_entry(entry,
+                                   range.FileOffset.QuadPart,
+                                   range.Length.QuadPart);
+                       }
+                       break;
+               } else {
+                       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                           "DeviceIoControl Failed: %lu", GetLastError());
+                       exit_sts = ARCHIVE_FAILED;
+                       goto exit_setup_sparse;
+               }
+       }
+exit_setup_sparse:
+       if (initial_fd != fd)
+               close(fd);
+       free(outranges);
+
+       return (exit_sts);
+}
+
+#else
+
+/*
+ * Generic (stub) sparse support.
+ */
+static int
+setup_sparse(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       (void)a;     /* UNUSED */
+       (void)entry; /* UNUSED */
+       (void)fd;    /* UNUSED */
+       return (ARCHIVE_OK);
+}
+
+#endif
index dae13dc6e0060de1a68fba046dcd76b24f19d324..aa2169b2dbcc3735e5a03c4777a6d27919e56e0b 100644 (file)
@@ -420,6 +420,14 @@ archive_read_format_tar_read_header(struct archive_read *a,
         */
        if (tar->sparse_list == NULL)
                gnu_add_sparse_entry(tar, 0, tar->entry_bytes_remaining);
+       else {
+               struct sparse_block *sb;
+
+               for (sb = tar->sparse_list; sb != NULL; sb = sb->next) {
+                       archive_entry_sparse_add_entry(entry,
+                           sb->offset, sb->remaining);
+               }
+       }
 
        if (r == ARCHIVE_OK) {
                /*
index 626340a4161f5bf08df5f9ed755de547b501a971..f4d2ebf57f20e4d677cf1644c6a8ab1036c70d16 100644 (file)
@@ -87,6 +87,7 @@ IF(ENABLE_TEST)
     test_read_position.c
     test_read_truncated.c
     test_read_uu.c
+    test_sparse_basic.c
     test_tar_filenames.c
     test_tar_large.c
     test_ustar_filenames.c
diff --git a/libarchive/test/test_sparse_basic.c b/libarchive/test/test_sparse_basic.c
new file mode 100644 (file)
index 0000000..39cc3ce
--- /dev/null
@@ -0,0 +1,333 @@
+/*-
+ * Copyright (c) 2010 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/*
+ * NOTE: On FreeBSD and Solaris, this test needs ZFS.
+ * You may should perfom this test as
+ * 'TMPDIR=<a directory on the ZFS> libarchive_test'.
+ */
+
+struct sparse {
+       enum { DATA, HOLE, END } type;
+       size_t  size;
+};
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+
+/*
+ * Create a sparse file on Windows.
+ */
+
+#define        PATH_MAX        MAX_PATH
+#define        getcwd          _getcwd
+
+static int
+is_sparse_supported(const char *path)
+{
+       char root[MAX_PATH+1];
+       char vol[MAX_PATH+1];
+       char sys[MAX_PATH+1];
+       DWORD flags;
+       BOOL r;
+
+       strncpy(root, path, sizeof(root)-1);
+       if (((root[0] >= 'c' && root[0] <= 'z') ||
+           (root[0] >= 'C' && root[0] <= 'Z')) &&
+               root[1] == ':' &&
+           (root[2] == '\\' || root[2] == '/'))
+               root[3] = '\0';
+       else
+               return (0);
+       assertEqualInt((r = GetVolumeInformation(root, vol,
+           sizeof(vol), NULL, NULL, &flags, sys, sizeof(sys))), 1);
+       return (r != 0 && (flags & FILE_SUPPORTS_SPARSE_FILES) != 0);
+}
+
+static void
+create_sparse_file(const char *path, const struct sparse *s)
+{
+       char buff[1024];
+       HANDLE handle;
+       DWORD dmy;
+
+       memset(buff, ' ', sizeof(buff));
+
+       handle = CreateFileA(path, GENERIC_WRITE, 0,
+           NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL,
+           NULL);
+       assert(handle != INVALID_HANDLE_VALUE);
+       assert(DeviceIoControl(handle, FSCTL_SET_SPARSE, NULL, 0,
+           NULL, 0, &dmy, NULL) != 0);
+       while (s->type != END) {
+               if (s->type == HOLE) {
+                       LARGE_INTEGER distance;
+
+                       distance.QuadPart = s->size;
+                       assert(SetFilePointerEx(handle, distance,
+                           NULL, FILE_CURRENT) != 0);
+               } else {
+                       DWORD w, wr, size;
+
+                       size = s->size;
+                       while (size) {
+                               if (size > sizeof(buff))
+                                       w = sizeof(buff);
+                               else
+                                       w = size;
+                               assert(WriteFile(handle, buff, w, &wr, NULL) != 0);
+                               size -= wr;
+                       }
+               }
+               s++;
+       }
+       assertEqualInt(CloseHandle(handle), 1);
+}
+
+#else
+
+#if defined(_PC_MIN_HOLE_SIZE)
+
+/*
+ * FreeBSD and Solaris can detect 'hole' of a sparse file
+ * through lseek(HOLE) on ZFS. (UFS does not support yet)
+ */
+
+static int
+is_sparse_supported(const char *path)
+{
+       return (pathconf(path, _PC_MIN_HOLE_SIZE) > 0);
+}
+
+#elif defined(__linux__)
+
+/*
+ * FIEMAP, which can detect 'hole' of a sparse file, has
+ * been supported from 2.6.28
+ */
+
+static int
+is_sparse_supported(const char *path)
+{
+       struct utsname ut;
+       char *p, *e;
+       long d;
+
+       memset(&ut, 0, sizeof(ut));
+       assertEqualInt(uname(&ut), 0);
+       p = ut.release;
+       d = strtol(p, &e, 10);
+       if (d < 2 || *e != '.')
+               return (0);
+       p = e + 1;
+       d = strtol(p, &e, 10);
+       if (d < 6 || *e != '.')
+               return (0);
+       p = e + 1;
+       d = strtol(p, NULL, 10);
+       return (d >= 28);
+}
+
+#else
+
+/*
+ * Other system may do not have the API such as lseek(HOLE),
+ * which detect 'hole' of a sparse file.
+ */
+
+static int
+is_sparse_supported(const char *path)
+{
+       return (0);
+}
+
+#endif
+
+/*
+ * Create a sparse file on POSIX like system.
+ */
+
+static void
+create_sparse_file(const char *path, const struct sparse *s)
+{
+       char buff[1024];
+       int fd;
+
+       memset(buff, ' ', sizeof(buff));
+       assert((fd = open(path, O_CREAT | O_WRONLY, 0600)) != -1);
+       while (s->type != END) {
+               if (s->type == HOLE) {
+                       assert(lseek(fd, s->size, SEEK_CUR) != (off_t)-1);
+               } else {
+                       size_t w, size;
+
+                       size = s->size;
+                       while (size) {
+                               if (size > sizeof(buff))
+                                       w = sizeof(buff);
+                               else
+                                       w = size;
+                               assert(write(fd, buff, w) != (ssize_t)-1);
+                               size -= w;
+                       }
+               }
+               s++;
+       }
+       close(fd);
+}
+
+#endif
+
+static void
+verify_sparse_file(struct archive *a, const char *path,
+    const struct sparse *sparse, int blocks)
+{
+       struct stat stb, *st;
+       struct archive_entry *ae;
+
+       st = &stb;
+       create_sparse_file(path, sparse);
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       st = NULL;
+#else
+       assertEqualInt(stat(path, &stb), 0);
+#endif
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_pathname(ae, path);
+       archive_entry_copy_sourcepath(ae, path);
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, st));
+       /* Verify the number of holes only, not its offset nor its
+        * length because those alignments are deeply dependence on
+        * its filesystem. */ 
+       assertEqualInt(blocks, archive_entry_sparse_count(ae));
+       archive_entry_free(ae);
+}
+
+DEFINE_TEST(test_sparse_basic)
+{
+       char cwd[PATH_MAX+1];
+       char path[PATH_MAX+1];
+       char *p;
+       struct archive *a;
+       /*
+        * The alignment of the hole of sparse files deeply depends
+        * on filesystem. In my experience, sparse_file2 test with
+        * 204800 bytes hole size did not pass on ZFS and the result
+        * of that test seemed the size was too small, thus you should
+        * keep a hole size more than 409600 bytes to pass this test
+        * on all platform.
+        */
+       const struct sparse sparse_file0[] = {
+               { DATA,  1024 }, { HOLE,   2048000 },
+               { DATA,  2048 }, { HOLE,   2048000 },
+               { DATA,  4096 }, { HOLE,  20480000 },
+               { DATA,  8192 }, { HOLE, 204800000 },
+               { DATA,     1 }, { END, 0 }
+       };
+       const struct sparse sparse_file1[] = {
+               { HOLE, 409600 }, { DATA, 1 },
+               { HOLE, 409600 }, { DATA, 1 },
+               { HOLE, 409600 }, { END,  0 }
+       };
+       const struct sparse sparse_file2[] = {
+               { HOLE, 409600 * 1 }, { DATA, 1024 },
+               { HOLE, 409600 * 2 }, { DATA, 1024 },
+               { HOLE, 409600 * 3 }, { DATA, 1024 },
+               { HOLE, 409600 * 4 }, { DATA, 1024 },
+               { HOLE, 409600 * 5 }, { DATA, 1024 },
+               { HOLE, 409600 * 6 }, { DATA, 1024 },
+               { HOLE, 409600 * 7 }, { DATA, 1024 },
+               { HOLE, 409600 * 8 }, { DATA, 1024 },
+               { HOLE, 409600 * 9 }, { DATA, 1024 },
+               { HOLE, 409600 * 10}, { DATA, 1024 },/* 10 */
+               { HOLE, 409600 * 1 }, { DATA, 1024 * 1 },
+               { HOLE, 409600 * 2 }, { DATA, 1024 * 2 },
+               { HOLE, 409600 * 3 }, { DATA, 1024 * 3 },
+               { HOLE, 409600 * 4 }, { DATA, 1024 * 4 },
+               { HOLE, 409600 * 5 }, { DATA, 1024 * 5 },
+               { HOLE, 409600 * 6 }, { DATA, 1024 * 6 },
+               { HOLE, 409600 * 7 }, { DATA, 1024 * 7 },
+               { HOLE, 409600 * 8 }, { DATA, 1024 * 8 },
+               { HOLE, 409600 * 9 }, { DATA, 1024 * 9 },
+               { HOLE, 409600 * 10}, { DATA, 1024 * 10},/* 20 */
+               { END,  0 }
+       };
+       const struct sparse sparse_file3[] = {
+               /* This hole size is too small to create a sparse
+                * files for almost filesystem. */
+               { HOLE,  1024 }, { DATA, 10240 },
+               { END,  0 }
+       };
+
+       /* Make a filename template. */
+       if (!assert(getcwd(cwd, sizeof(cwd)-1) != NULL))
+               return;
+       while (cwd[strlen(cwd) - 1] == '\n')
+               cwd[strlen(cwd) - 1] = '\0';
+       strncpy(path, cwd,  sizeof(path)-1);
+       if (!assert(strlen(path) + 7 <= sizeof(path)))
+               return;/* a filename is too long */
+       if (!is_sparse_supported(path)) {
+               skipping("This filesystem or platform do not support "
+                   "the reporting of the holes of a sparse file through "
+                   "API such as lseek(HOLE)");
+               return;
+       }
+       p = path + strlen(path);
+
+       assert((a = archive_read_disk_new()) != NULL);
+
+       strcpy(p, "/file0");
+       verify_sparse_file(a, path, sparse_file0, 5);
+
+       strcpy(p, "/file1");
+       verify_sparse_file(a, path, sparse_file1, 2);
+
+       strcpy(p, "/file2");
+       verify_sparse_file(a, path, sparse_file2, 20);
+
+       strcpy(p, "/file3");
+       verify_sparse_file(a, path, sparse_file3, 1);
+
+       archive_read_finish(a);
+}