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)
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)
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)
#
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 \
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 \
/* 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}
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],
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
/*-
* 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
#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
#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"
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,
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);
}
#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
*/
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) {
/*
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
--- /dev/null
+/*-
+ * 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);
+}