]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Support SEEK_HOLE under Linux, fallback to FIEMAP
authorMartin Matuska <martin@matuska.org>
Wed, 26 Oct 2016 21:18:37 +0000 (23:18 +0200)
committerMartin Matuska <martin@matuska.org>
Mon, 7 Nov 2016 14:06:01 +0000 (15:06 +0100)
Closes #814

libarchive/archive_read_disk_entry_from_file.c
libarchive/test/test_sparse_basic.c

index 4c7f04873d06b4d005547a6545cde3039559e703..82ef21322eac852afce834ca25fc405f8d197eea 100644 (file)
@@ -125,6 +125,10 @@ 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);
+#if defined(HAVE_LINUX_FIEMAP_H)
+static int setup_sparse_fiemap(struct archive_read_disk *,
+    struct archive_entry *, int *fd);
+#endif
 
 int
 archive_read_disk_entry_from_file(struct archive *_a,
@@ -1125,7 +1129,7 @@ setup_xattrs(struct archive_read_disk *a,
 #if defined(HAVE_LINUX_FIEMAP_H)
 
 /*
- * Linux sparse interface.
+ * Linux FIEMAP sparse interface.
  *
  * The FIEMAP ioctl returns an "extent" for each physical allocation
  * on disk.  We need to process those to generate a more compact list
@@ -1140,7 +1144,7 @@ setup_xattrs(struct archive_read_disk *a,
  */
 
 static int
-setup_sparse(struct archive_read_disk *a,
+setup_sparse_fiemap(struct archive_read_disk *a,
     struct archive_entry *entry, int *fd)
 {
        char buff[4096];
@@ -1192,7 +1196,7 @@ setup_sparse(struct archive_read_disk *a,
                        /* When something error happens, it is better we
                         * should return ARCHIVE_OK because an earlier
                         * version(<2.6.28) cannot perfom FS_IOC_FIEMAP. */
-                       goto exit_setup_sparse;
+                       goto exit_setup_sparse_fiemap;
                }
                if (fm->fm_mapped_extents == 0) {
                        if (iters == 0) {
@@ -1227,14 +1231,24 @@ setup_sparse(struct archive_read_disk *a,
                } else
                        break;
        }
-exit_setup_sparse:
+exit_setup_sparse_fiemap:
        return (exit_sts);
 }
 
-#elif defined(SEEK_HOLE) && defined(SEEK_DATA) && defined(_PC_MIN_HOLE_SIZE)
+#if !defined(SEEK_HOLE) || !defined(SEEK_DATA)
+static int
+setup_sparse(struct archive_read_disk *a,
+    struct archive_entry *entry, int *fd)
+{
+       return setup_sparse_fiemap(a, entry, fd);
+}
+#endif
+#endif /* defined(HAVE_LINUX_FIEMAP_H) */
+
+#if defined(SEEK_HOLE) && defined(SEEK_DATA)
 
 /*
- * FreeBSD and Solaris sparse interface.
+ * SEEK_HOLE sparse interface (FreeBSD, Linux, Solaris)
  */
 
 static int
@@ -1242,8 +1256,8 @@ setup_sparse(struct archive_read_disk *a,
     struct archive_entry *entry, int *fd)
 {
        int64_t size;
-       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 */
+       off_t initial_off;
+       off_t off_s, off_e;
        int exit_sts = ARCHIVE_OK;
        int check_fully_sparse = 0;
 
@@ -1269,8 +1283,10 @@ setup_sparse(struct archive_read_disk *a,
        }
 
        if (*fd >= 0) {
+#ifdef _PC_MIN_HOLE_SIZE
                if (fpathconf(*fd, _PC_MIN_HOLE_SIZE) <= 0)
                        return (ARCHIVE_OK);
+#endif
                initial_off = lseek(*fd, 0, SEEK_CUR);
                if (initial_off != 0)
                        lseek(*fd, 0, SEEK_SET);
@@ -1281,8 +1297,10 @@ setup_sparse(struct archive_read_disk *a,
                if (path == NULL)
                        path = archive_entry_pathname(entry);
                        
+#ifdef _PC_MIN_HOLE_SIZE
                if (pathconf(path, _PC_MIN_HOLE_SIZE) <= 0)
                        return (ARCHIVE_OK);
+#endif
                *fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
                if (*fd < 0) {
                        archive_set_error(&a->archive, errno,
@@ -1293,6 +1311,19 @@ setup_sparse(struct archive_read_disk *a,
                initial_off = 0;
        }
 
+#ifndef _PC_MIN_HOLE_SIZE
+       /* Check if the underlying filesystem supports seek hole */
+       off_s = lseek(*fd, 0, SEEK_HOLE);
+       if (off_s < 0)
+#if defined(HAVE_LINUX_FIEMAP_H)
+               return setup_sparse_fiemap(a, entry, fd);
+#else
+               goto exit_setup_sparse;
+#endif
+       else if (off_s > 0)
+               lseek(*fd, 0, SEEK_SET);
+#endif
+
        off_s = 0;
        size = archive_entry_size(entry);
        while (off_s < size) {
index 3cea4595c95fd7980f1cce90e05abe49deaba4c8..5959c9ce8a2a7e77f434e7cf927a262e2a52a545 100644 (file)
@@ -146,28 +146,14 @@ create_sparse_file(const char *path, const struct sparse *s)
 
 #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__)&& defined(HAVE_LINUX_FIEMAP_H)
-
+#if defined(HAVE_LINUX_FIEMAP_H)
 /*
  * FIEMAP, which can detect 'hole' of a sparse file, has
  * been supported from 2.6.28
  */
 
 static int
-is_sparse_supported(const char *path)
+is_sparse_supported_fiemap(const char *path)
 {
        const struct sparse sparse_file[] = {
                /* This hole size is too small to create a sparse
@@ -198,6 +184,57 @@ is_sparse_supported(const char *path)
        return (r >= 0);
 }
 
+#if !defined(SEEK_HOLE) || !defined(SEEK_DATA)
+static int
+is_sparse_supported(const char *path)
+{
+       return is_sparse_supported_fiemap(const char *path)
+}
+#endif
+#endif
+
+#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(SEEK_HOLE) && defined(SEEK_DATA)
+
+static int
+is_sparse_supported(const char *path)
+{
+       const struct sparse sparse_file[] = {
+               /* This hole size is too small to create a sparse
+                * files for almost filesystem. */
+               { HOLE,  1024 }, { DATA, 10240 },
+               { END,  0 }
+       };
+       int fd, r;
+       const char *testfile = "can_sparse";
+
+       (void)path; /* UNUSED */
+       create_sparse_file(testfile, sparse_file);
+       fd = open(testfile,  O_RDWR);
+       if (fd < 0)
+               return (0);
+       r = lseek(fd, 0, SEEK_HOLE);
+       close(fd);
+       unlink(testfile);
+#if defined(HAVE_LINUX_FIEMAP_H)
+       if (r < 0)
+               return (is_sparse_supported_fiemap(path));
+#endif
+       return (r >= 0);
+}
+
 #else
 
 /*