From: Michihiro NAKAJIMA Date: Tue, 2 Feb 2010 11:09:17 +0000 (-0500) Subject: Determine sparse files through API such as lseek(HOLE). X-Git-Tag: v3.0.0a~1292 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d216d028a78e56a37bab9e42a2f17f28714a6535;p=thirdparty%2Flibarchive.git Determine sparse files through API such as lseek(HOLE). SVN-Revision: 1856 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index f844e7eb1..ea1272cd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) # diff --git a/Makefile.am b/Makefile.am index 88cea1570..a2268f75b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/build/cmake/config.h.in b/build/cmake/config.h.in index dadda8605..b9ff03fa7 100644 --- a/build/cmake/config.h.in +++ b/build/cmake/config.h.in @@ -265,6 +265,9 @@ /* Define to 1 if you have the link() function. */ #cmakedefine HAVE_LINK 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LINUX_FIEMAP_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LINUX_FS_H 1 @@ -536,6 +539,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_UTIME_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTSNAME_H 1 + /* Define to 1 if you have that is POSIX.1 compatible. */ #cmakedefine HAVE_SYS_WAIT_H 1 @@ -608,6 +614,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_WINDOWS_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WINIOCTL_H 1 + /* Define to 1 if you have _CrtSetReportMode in */ #cmakedefine HAVE__CrtSetReportMode 1 @@ -667,6 +676,10 @@ #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} diff --git a/configure.ac b/configure.ac index e4beae79e..513c46aaf 100644 --- a/configure.ac +++ b/configure.ac @@ -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], diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt index 459a5ddbb..0ca57e616 100644 --- a/libarchive/CMakeLists.txt +++ b/libarchive/CMakeLists.txt @@ -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 diff --git a/libarchive/archive_read_disk_entry_from_file.c b/libarchive/archive_read_disk_entry_from_file.c index de218c50a..054a89384 100644 --- a/libarchive/archive_read_disk_entry_from_file.c +++ b/libarchive/archive_read_disk_entry_from_file.c @@ -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 #endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif #ifdef HAVE_SYS_PARAM_H #include #endif @@ -54,11 +58,23 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_read_disk_entry_from_file.c 2010 #ifdef HAVE_ERRNO_H #include #endif +#ifdef HAVE_FCNTL_H +#include +#endif #ifdef HAVE_LIMITS_H #include #endif -#ifdef HAVE_WINDOWS_H -#include +#ifdef HAVE_LINUX_FIEMAP_H +#include +#endif +#ifdef HAVE_LINUX_FS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_WINIOCTL_H +#include #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 diff --git a/libarchive/archive_read_support_format_tar.c b/libarchive/archive_read_support_format_tar.c index dae13dc6e..aa2169b2d 100644 --- a/libarchive/archive_read_support_format_tar.c +++ b/libarchive/archive_read_support_format_tar.c @@ -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) { /* diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index 626340a41..f4d2ebf57 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -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 index 000000000..39cc3ce56 --- /dev/null +++ b/libarchive/test/test_sparse_basic.c @@ -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 +#endif +#ifdef HAVE_SYS_UTSNAME_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +/* + * NOTE: On FreeBSD and Solaris, this test needs ZFS. + * You may should perfom this test as + * 'TMPDIR= 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); +}