[Libguestfs] [PATCH nbdkit 4/6] cow: Implement extents.

Richard W.M. Jones
Tue, 26 Jan 2021 21:51:50 -0000

We implement extents in the filter by checking block-by-block if the
block is present in the overlay (in which case it's non-sparse) or if
not calling through to the underlying plugin.
---
 filters/cow/blk.h |   3 ++
 filters/cow/blk.c |  10 +++++
 filters/cow/cow.c | 102 ++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 111 insertions(+), 4 deletions(-)

diff --git a/filters/cow/blk.h b/filters/cow/blk.h
index 28ef3b4a..5eb30794 100644
--- a/filters/cow/blk.h
+++ b/filters/cow/blk.h
@@ -54,6 +54,9 @@ extern void blk_free (void);
 /* Allocate or resize the overlay and bitmap. */
 extern int blk_set_size (uint64_t new_size);

+/* Returns the status of the block in the overlay. */
+extern void blk_status (uint64_t blknum, bool *present, bool *trimmed);
+
 /* Read a single block from the overlay or plugin. */
 extern int blk_read (struct nbdkit_next_ops *next_ops, void *nxdata,
                      uint64_t blknum, uint8_t *block, int *err)
diff --git a/filters/cow/blk.c b/filters/cow/blk.c
index 9e85920f..4a8adfb9 100644
--- a/filters/cow/blk.c
+++ b/filters/cow/blk.c
@@ -177,6 +177,16 @@ blk_set_allocated (uint64_t blknum)
   bitmap_set_blk (&bm, blknum, true);
 }

+/* This is a bit of a hack since usually this information is hidden in
+ * the blk module.  However it is needed when calculating extents.
+ */
+void
+blk_status (uint64_t blknum, bool *present, bool *trimmed)
+{
+  *present = blk_is_allocated (blknum);
+  *trimmed = false;
+}
+
 /* These are the block operations.  They always read or write a single
  * whole block of size ?blksize?.
  */
diff --git a/filters/cow/cow.c b/filters/cow/cow.c
index 92358375..d12565e6 100644
--- a/filters/cow/cow.c
+++ b/filters/cow/cow.c
@@ -141,9 +141,6 @@ cow_prepare (struct nbdkit_next_ops *next_ops, void *nxdata,
   return r >= 0 ? 0 : -1;
 }

-/* Whatever the underlying plugin can or can't do, we can write, we
- * cannot trim or detect extents, and we can flush.
- */
 static int
 cow_can_write (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
 {
@@ -159,7 +156,7 @@ cow_can_trim (struct nbdkit_next_ops *next_ops, void
*nxdata, void *handle)
 static int
 cow_can_extents (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
 {
-  return 0;
+  return 1;
 }

 static int
@@ -499,6 +496,102 @@ cow_cache (struct nbdkit_next_ops *next_ops, void *nxdata,
   return 0;
 }

+/* Extents. */
+static int
+cow_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
+             void *handle, uint32_t count, uint64_t offset, uint32_t flags,
+             struct nbdkit_extents *extents, int *err)
+{
+  const bool can_extents = next_ops->can_extents (nxdata);
+  const bool req_one = flags & NBDKIT_FLAG_REQ_ONE;
+  uint64_t end;
+  uint64_t blknum;
+
+  /* To make this easier, align the requested extents to whole blocks. */
+  end = offset + count;
+  offset = ROUND_DOWN (offset, BLKSIZE);
+  end = ROUND_UP (end, BLKSIZE);
+  count  = end - offset;
+  blknum = offset / BLKSIZE;
+
+  assert (IS_ALIGNED (offset, BLKSIZE));
+  assert (IS_ALIGNED (count, BLKSIZE));
+  assert (count > 0);           /* We must make forward progress. */
+
+  /* We hold the lock for the whole time, even when requesting extents
+   * from the plugin, because we want to present an atomic picture of
+   * the current state.
+   */
+  ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
+
+  while (count > 0) {
+    bool present, trimmed;
+    struct nbdkit_extent e;
+
+    blk_status (blknum, &present, &trimmed);
+
+    /* Present in the overlay. */
+    if (present) {
+      e.offset = offset;
+      e.length = BLKSIZE;
+
+      if (trimmed)
+        e.type = NBDKIT_EXTENT_HOLE|NBDKIT_EXTENT_ZERO;
+      else
+        e.type = 0;
+
+      if (nbdkit_add_extent (extents, e.offset, e.length, e.type) == -1) {
+        *err = errno;
+        return -1;
+      }
+    }
+
+    /* Not present in the overlay, but we can ask the plugin */
+    else if (can_extents) {
+      size_t i;
+
+      CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents2 +
nbdkit_extents_full (next_ops, nxdata, BLKSIZE, offset, flags, err);
+      if (extents2 == NULL)
+        return -1;
+
+      for (i = 0; i < nbdkit_extents_count (extents2); ++i) {
+        e = nbdkit_get_extent (extents2, i);
+        if (nbdkit_add_extent (extents, e.offset, e.length, e.type) == -1) {
+          *err = errno;
+          return -1;
+        }
+      }
+    }
+
+    /* Otherwise assume the block is non-sparse. */
+    else {
+      e.offset = offset;
+      e.length = BLKSIZE;
+      e.type = 0;
+
+      if (nbdkit_add_extent (extents, e.offset, e.length, e.type) == -1) {
+        *err = errno;
+        return -1;
+      }
+    }
+
+    blknum++;
+    offset += BLKSIZE;
+    count -= BLKSIZE;
+
+    /* If the caller only wanted the first extent, and we've managed
+     * to add at least one extent to the list, then we can drop out
+     * now.  (Note calling nbdkit_add_extent above does not mean the
+     * extent got added since it might be before the first offset.)
+     */
+    if (req_one && nbdkit_extents_count (extents) > 0)
+      break;
+  }
+
+  return 0;
+}
+
 static struct nbdkit_filter filter = {
   .name              = "cow",
   .longname          = "nbdkit copy-on-write (COW) filter",
@@ -521,6 +614,7 @@ static struct nbdkit_filter filter = {
   .zero              = cow_zero,
   .flush             = cow_flush,
   .cache             = cow_cache,
+  .extents           = cow_extents,
 };

 NBDKIT_REGISTER_FILTER(filter)
--
2.29.0.rc2