958e1b
From 04981be7338ba396410cdab91612c17d6383d3a3 Mon Sep 17 00:00:00 2001
09b1b5
From: Max Reitz <mreitz@redhat.com>
09b1b5
Date: Tue, 18 Nov 2014 15:30:14 +0100
958e1b
Subject: [PATCH 35/41] block/raw-posix: Try both FIEMAP and SEEK_HOLE
09b1b5
09b1b5
Message-id: <1416324620-16229-2-git-send-email-mreitz@redhat.com>
09b1b5
Patchwork-id: 62436
09b1b5
O-Subject: [RHEL-7.1/7.0.z qemu-kvm PATCH v3 1/7] block/raw-posix: Try both FIEMAP and SEEK_HOLE
958e1b
Bugzilla: 1160237
09b1b5
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
09b1b5
RH-Acked-by: Kevin Wolf <kwolf@redhat.com>
09b1b5
RH-Acked-by: Markus Armbruster <armbru@redhat.com>
09b1b5
09b1b5
The current version of raw-posix always uses ioctl(FS_IOC_FIEMAP) if
09b1b5
FIEMAP is available; lseek with SEEK_HOLE/SEEK_DATA are not even
09b1b5
compiled in in this case. However, there may be implementations which
09b1b5
support the latter but not the former (e.g., NFSv4.2) as well as vice
09b1b5
versa.
09b1b5
09b1b5
To cover both cases, try FIEMAP first (as this will return -ENOTSUP if
09b1b5
not supported instead of returning a failsafe value (everything
09b1b5
allocated as a single extent)) and if that does not work, fall back to
09b1b5
SEEK_HOLE/SEEK_DATA.
09b1b5
09b1b5
Signed-off-by: Max Reitz <mreitz@redhat.com>
09b1b5
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
09b1b5
(cherry picked from commit 4f11aa8a40351b28c0e67c7276e0003b38cc46ac)
09b1b5
09b1b5
Signed-off-by: Max Reitz <mreitz@redhat.com>
09b1b5
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
09b1b5
---
09b1b5
 block/raw-posix.c | 127 +++++++++++++++++++++++++++++++++---------------------
09b1b5
 1 file changed, 77 insertions(+), 50 deletions(-)
09b1b5
09b1b5
diff --git a/block/raw-posix.c b/block/raw-posix.c
958e1b
index cfe7452..5f57412 100644
09b1b5
--- a/block/raw-posix.c
09b1b5
+++ b/block/raw-posix.c
958e1b
@@ -147,6 +147,9 @@ typedef struct BDRVRawState {
09b1b5
     bool has_discard:1;
09b1b5
     bool has_write_zeroes:1;
09b1b5
     bool discard_zeroes:1;
09b1b5
+#ifdef CONFIG_FIEMAP
09b1b5
+    bool skip_fiemap;
09b1b5
+#endif
09b1b5
 } BDRVRawState;
09b1b5
 
09b1b5
 typedef struct BDRVRawReopenState {
958e1b
@@ -1305,53 +1308,29 @@ out:
09b1b5
     return result;
09b1b5
 }
09b1b5
 
09b1b5
-/*
09b1b5
- * Returns true iff the specified sector is present in the disk image. Drivers
09b1b5
- * not implementing the functionality are assumed to not support backing files,
09b1b5
- * hence all their sectors are reported as allocated.
09b1b5
- *
09b1b5
- * If 'sector_num' is beyond the end of the disk image the return value is 0
09b1b5
- * and 'pnum' is set to 0.
09b1b5
- *
09b1b5
- * 'pnum' is set to the number of sectors (including and immediately following
09b1b5
- * the specified sector) that are known to be in the same
09b1b5
- * allocated/unallocated state.
09b1b5
- *
09b1b5
- * 'nb_sectors' is the max value 'pnum' should be set to.  If nb_sectors goes
09b1b5
- * beyond the end of the disk image it will be clamped.
09b1b5
- */
09b1b5
-static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
09b1b5
-                                            int64_t sector_num,
09b1b5
-                                            int nb_sectors, int *pnum)
09b1b5
+static int64_t try_fiemap(BlockDriverState *bs, off_t start, off_t *data,
09b1b5
+                          off_t *hole, int nb_sectors, int *pnum)
09b1b5
 {
09b1b5
-    off_t start, data, hole;
09b1b5
-    int64_t ret;
09b1b5
-
09b1b5
-    ret = fd_open(bs);
09b1b5
-    if (ret < 0) {
09b1b5
-        return ret;
09b1b5
-    }
09b1b5
-
09b1b5
-    start = sector_num * BDRV_SECTOR_SIZE;
09b1b5
-    ret = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | start;
09b1b5
-
09b1b5
 #ifdef CONFIG_FIEMAP
09b1b5
-
09b1b5
     BDRVRawState *s = bs->opaque;
09b1b5
+    int64_t ret = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | start;
09b1b5
     struct {
09b1b5
         struct fiemap fm;
09b1b5
         struct fiemap_extent fe;
09b1b5
     } f;
09b1b5
 
09b1b5
+    if (s->skip_fiemap) {
09b1b5
+        return -ENOTSUP;
09b1b5
+    }
09b1b5
+
09b1b5
     f.fm.fm_start = start;
09b1b5
     f.fm.fm_length = (int64_t)nb_sectors * BDRV_SECTOR_SIZE;
09b1b5
     f.fm.fm_flags = 0;
09b1b5
     f.fm.fm_extent_count = 1;
09b1b5
     f.fm.fm_reserved = 0;
09b1b5
     if (ioctl(s->fd, FS_IOC_FIEMAP, &f) == -1) {
09b1b5
-        /* Assume everything is allocated.  */
09b1b5
-        *pnum = nb_sectors;
09b1b5
-        return ret;
09b1b5
+        s->skip_fiemap = true;
09b1b5
+        return -errno;
09b1b5
     }
09b1b5
 
09b1b5
     if (f.fm.fm_mapped_extents == 0) {
958e1b
@@ -1359,44 +1338,92 @@ static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
09b1b5
          * f.fm.fm_start + f.fm.fm_length must be clamped to the file size!
09b1b5
          */
09b1b5
         off_t length = lseek(s->fd, 0, SEEK_END);
09b1b5
-        hole = f.fm.fm_start;
09b1b5
-        data = MIN(f.fm.fm_start + f.fm.fm_length, length);
09b1b5
+        *hole = f.fm.fm_start;
09b1b5
+        *data = MIN(f.fm.fm_start + f.fm.fm_length, length);
09b1b5
     } else {
09b1b5
-        data = f.fe.fe_logical;
09b1b5
-        hole = f.fe.fe_logical + f.fe.fe_length;
09b1b5
+        *data = f.fe.fe_logical;
09b1b5
+        *hole = f.fe.fe_logical + f.fe.fe_length;
09b1b5
         if (f.fe.fe_flags & FIEMAP_EXTENT_UNWRITTEN) {
09b1b5
             ret |= BDRV_BLOCK_ZERO;
09b1b5
         }
09b1b5
     }
09b1b5
 
09b1b5
-#elif defined SEEK_HOLE && defined SEEK_DATA
09b1b5
+    return ret;
09b1b5
+#else
09b1b5
+    return -ENOTSUP;
09b1b5
+#endif
09b1b5
+}
09b1b5
 
09b1b5
+static int64_t try_seek_hole(BlockDriverState *bs, off_t start, off_t *data,
09b1b5
+                             off_t *hole, int *pnum)
09b1b5
+{
09b1b5
+#if defined SEEK_HOLE && defined SEEK_DATA
09b1b5
     BDRVRawState *s = bs->opaque;
09b1b5
 
09b1b5
-    hole = lseek(s->fd, start, SEEK_HOLE);
09b1b5
-    if (hole == -1) {
09b1b5
+    *hole = lseek(s->fd, start, SEEK_HOLE);
09b1b5
+    if (*hole == -1) {
09b1b5
         /* -ENXIO indicates that sector_num was past the end of the file.
09b1b5
          * There is a virtual hole there.  */
09b1b5
         assert(errno != -ENXIO);
09b1b5
 
09b1b5
-        /* Most likely EINVAL.  Assume everything is allocated.  */
09b1b5
-        *pnum = nb_sectors;
09b1b5
-        return ret;
09b1b5
+        return -errno;
09b1b5
     }
09b1b5
 
09b1b5
-    if (hole > start) {
09b1b5
-        data = start;
09b1b5
+    if (*hole > start) {
09b1b5
+        *data = start;
09b1b5
     } else {
09b1b5
         /* On a hole.  We need another syscall to find its end.  */
09b1b5
-        data = lseek(s->fd, start, SEEK_DATA);
09b1b5
-        if (data == -1) {
09b1b5
-            data = lseek(s->fd, 0, SEEK_END);
09b1b5
+        *data = lseek(s->fd, start, SEEK_DATA);
09b1b5
+        if (*data == -1) {
09b1b5
+            *data = lseek(s->fd, 0, SEEK_END);
09b1b5
         }
09b1b5
     }
09b1b5
+
09b1b5
+    return BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | start;
09b1b5
 #else
09b1b5
-    data = 0;
09b1b5
-    hole = start + nb_sectors * BDRV_SECTOR_SIZE;
09b1b5
+    return -ENOTSUP;
09b1b5
 #endif
09b1b5
+}
09b1b5
+
09b1b5
+/*
09b1b5
+ * Returns true iff the specified sector is present in the disk image. Drivers
09b1b5
+ * not implementing the functionality are assumed to not support backing files,
09b1b5
+ * hence all their sectors are reported as allocated.
09b1b5
+ *
09b1b5
+ * If 'sector_num' is beyond the end of the disk image the return value is 0
09b1b5
+ * and 'pnum' is set to 0.
09b1b5
+ *
09b1b5
+ * 'pnum' is set to the number of sectors (including and immediately following
09b1b5
+ * the specified sector) that are known to be in the same
09b1b5
+ * allocated/unallocated state.
09b1b5
+ *
09b1b5
+ * 'nb_sectors' is the max value 'pnum' should be set to.  If nb_sectors goes
09b1b5
+ * beyond the end of the disk image it will be clamped.
09b1b5
+ */
09b1b5
+static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
09b1b5
+                                                    int64_t sector_num,
09b1b5
+                                                    int nb_sectors, int *pnum)
09b1b5
+{
09b1b5
+    off_t start, data = 0, hole = 0;
09b1b5
+    int64_t ret;
09b1b5
+
09b1b5
+    ret = fd_open(bs);
09b1b5
+    if (ret < 0) {
09b1b5
+        return ret;
09b1b5
+    }
09b1b5
+
09b1b5
+    start = sector_num * BDRV_SECTOR_SIZE;
09b1b5
+
09b1b5
+    ret = try_fiemap(bs, start, &data, &hole, nb_sectors, pnum);
09b1b5
+    if (ret < 0) {
09b1b5
+        ret = try_seek_hole(bs, start, &data, &hole, pnum);
09b1b5
+        if (ret < 0) {
09b1b5
+            /* Assume everything is allocated. */
09b1b5
+            data = 0;
09b1b5
+            hole = start + nb_sectors * BDRV_SECTOR_SIZE;
09b1b5
+            ret = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID | start;
09b1b5
+        }
09b1b5
+    }
09b1b5
 
09b1b5
     if (data <= start) {
09b1b5
         /* On a data extent, compute sectors to the end of the extent.  */
09b1b5
-- 
09b1b5
1.8.3.1
09b1b5