We have several filters that would benefit from reporting extents to
the client that are always aligned to boundaries chosen by the filter,
regardless of whether the plugin reports extents at a finer
granularity. Add a new helper function to make the work easier,
without having to duplicate code in each filter. Any alignment block
that straddles more than one plugin extent is reported as a single
extent to the client, with a type determined by the intersection of
the underlying extents.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
docs/nbdkit-filter.pod | 18 ++++++++++
include/nbdkit-filter.h | 5 +++
server/extents.c | 80 ++++++++++++++++++++++++++++++++++++++++-
server/nbdkit.syms | 1 +
4 files changed, 103 insertions(+), 1 deletion(-)
diff --git a/docs/nbdkit-filter.pod b/docs/nbdkit-filter.pod
index 510781e1..066ca1c7 100644
--- a/docs/nbdkit-filter.pod
+++ b/docs/nbdkit-filter.pod
@@ -719,6 +719,24 @@ Returns the number of extents in the list.
Returns a copy of the C<i>'th extent.
+=head3 Enforcing alignment of an nbdkit_extents list
+
+A convenience function is provided to filters only which makes it
+easier to ensure that the client only encounters aligned extents.
+
+ int nbdkit_extents_aligned (struct nbdkit_next_ops *next_ops,
+ nbdkit_backend *nxdata,
+ uint32_t count, uint64_t offset,
+ uint32_t flags, uint32_t align,
+ struct nbdkit_extents *extents, int *err);
+
+Calls next_ops->extents as needed until at least C<align> bytes are
+obtained. Anywhere the underlying plugin returns differing extents
+within C<align> bytes, this function treats that portion of the disk
+as a single extent with zero and sparse status bits determined by the
+intersection of all underlying extents. It is an error to call this
+function with C<count> or C<offset> that is not already aligned.
+
=head2 C<.cache>
int (*cache) (struct nbdkit_next_ops *next_ops, void *nxdata,
diff --git a/include/nbdkit-filter.h b/include/nbdkit-filter.h
index d81186f5..229a54b4 100644
--- a/include/nbdkit-filter.h
+++ b/include/nbdkit-filter.h
@@ -116,6 +116,11 @@ extern void nbdkit_extents_free (struct nbdkit_extents *);
extern size_t nbdkit_extents_count (const struct nbdkit_extents *);
extern struct nbdkit_extent nbdkit_get_extent (const struct nbdkit_extents *,
size_t);
+extern int nbdkit_extents_aligned (struct nbdkit_next_ops *next_ops,
+ nbdkit_backend *nxdata,
+ uint32_t count, uint64_t offset,
+ uint32_t flags, uint32_t align,
+ struct nbdkit_extents *extents, int *err);
/* Filter struct. */
struct nbdkit_filter {
diff --git a/server/extents.c b/server/extents.c
index 4ab5946c..035497b5 100644
--- a/server/extents.c
+++ b/server/extents.c
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2019 Red Hat Inc.
+ * Copyright (C) 2019-2020 Red Hat Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -41,7 +41,10 @@
#include <errno.h>
#include <assert.h>
+#include "cleanup.h"
+#include "isaligned.h"
#include "minmax.h"
+#include "rounding.h"
#include "vector.h"
#include "internal.h"
@@ -206,3 +209,78 @@ nbdkit_add_extent (struct nbdkit_extents *exts,
return append_extent (exts, &e);
}
}
+
+/* Compute aligned extents on behalf of a filter. */
+int
+nbdkit_extents_aligned (struct nbdkit_next_ops *next_ops,
+ nbdkit_backend *nxdata,
+ uint32_t count, uint64_t offset,
+ uint32_t flags, uint32_t align,
+ struct nbdkit_extents *exts, int *err)
+{
+ size_t i;
+ struct nbdkit_extent e, e2;
+
+ if (!IS_ALIGNED(count | offset, align)) {
+ nbdkit_error ("nbdkit_extents_aligned: unaligned request");
+ *err = EINVAL;
+ return -1;
+ }
+
+ /* Perform an initial query, then scan for the first unaligned extent. */
+ if (next_ops->extents (nxdata, count, offset, flags, exts, err) == -1)
+ return -1;
+ for (i = 0; i < exts->extents.size; ++i) {
+ e = exts->extents.ptr[i];
+ if (!IS_ALIGNED(e.length, align)) {
+ /* If the unalignment is past align, just truncate and return early */
+ if (e.offset + e.length > offset + align) {
+ e.length = ROUND_DOWN (e.length, align);
+ exts->extents.size = i + !!e.length;
+ exts->next = e.offset + e.length;
+ break;
+ }
+
+ /* Otherwise, coalesce until we have at least align bytes, which
+ * may require further queries.
+ */
+ assert (i == 0);
+ while (e.length < align) {
+ if (exts->extents.size > 1) {
+ e.length += exts->extents.ptr[1].length;
+ e.type &= exts->extents.ptr[1].type;
+ extents_remove (&exts->extents, 1);
+ }
+ else {
+ /* The plugin needs a fresh extents object each time, but
+ * with care, we can merge it into the callers' extents.
+ */
+ extents tmp;
+ CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents2 = NULL;
+
+ extents2 = nbdkit_extents_new (e.offset + e.length, offset + align);
+ if (next_ops->extents (nxdata, offset + align - e.length,
+ e.offset + e.length,
+ flags & ~NBDKIT_FLAG_REQ_ONE,
+ extents2, err) == -1)
+ return -1;
+ e2 = extents2->extents.ptr[0];
+ assert (e2.offset == e.offset + e.length);
+ e2.offset = e.offset;
+ e2.length += e.length;
+ e2.type &= e.type;
+ e = e2;
+ tmp = exts->extents;
+ exts->extents = extents2->extents;
+ extents2->extents = tmp;
+ }
+ }
+ e.length = align;
+ exts->extents.size = 1;
+ exts->next = e.offset + e.length;
+ break;
+ }
+ }
+ /* Once we get here, all extents are aligned. */
+ return 0;
+}
diff --git a/server/nbdkit.syms b/server/nbdkit.syms
index 20c390a9..d62ad484 100644
--- a/server/nbdkit.syms
+++ b/server/nbdkit.syms
@@ -43,6 +43,7 @@
nbdkit_debug;
nbdkit_error;
nbdkit_export_name;
+ nbdkit_extents_aligned;
nbdkit_extents_count;
nbdkit_extents_free;
nbdkit_extents_new;
--
2.27.0