On Thu, Feb 17, 2022 at 02:36:42PM +0000, Richard W.M. Jones wrote:
This commit implements backend_block_size() and wires it up to a new
.block_size callback from the plugin through any filters on top.
This callback will allow plugins to pass their minimum, preferred and
maximum block size through the NBD protocol NBD_INFO_BLOCK_SIZE info
option.
The new function is not called from anywhere yet, so for the moment
this commit does nothing.
---
include/nbdkit-filter.h | 4 +++
include/nbdkit-plugin.h | 3 ++
server/internal.h | 9 ++++++
server/backend.c | 29 +++++++++++++++++++
server/filters.c | 17 +++++++++++
server/plugins.c | 62 +++++++++++++++++++++++++++++++++++++++++
6 files changed, 124 insertions(+)
I may still play with adding a 'bool strict' parameter as a followup
series on top of yours in the next couple of days; as long as I post
it prior to this going in a stable release, we will be able to see
what the difference would look like. Or after playing with it, I may
also decide that the work to add in the parameter just for the sake of
the nbd plugin turns out too complex, at which point living with your
signature would be the solution anyways. So I don't see a reason to
hold up your series just waiting for me to do a proposed followup on
top.
diff --git a/include/nbdkit-filter.h b/include/nbdkit-filter.h
index 8b587d70..4c620b4f 100644
--- a/include/nbdkit-filter.h
+++ b/include/nbdkit-filter.h
@@ -84,6 +84,8 @@ struct nbdkit_next_ops {
/* These callbacks are the same as normal plugin operations. */
int64_t (*get_size) (nbdkit_next *nxdata);
const char * (*export_description) (nbdkit_next *nxdata);
+ int (*block_size) (nbdkit_next *nxdata,
+ uint32_t *minimum, uint32_t *preferred, uint32_t *maximum);
I think even when we add 64-bit extensions that we will still want to
keep maximum as a 32-bit size, because it controls the buffer size we
pass to read and write. Part of the 64-bit extension work is figuring
out how a server can advertise that it supports 64-bit zero/trim/cache
even when it is limited to 32-bit (more specifically, 32M or 64M)
read/write; so I will probably be proposing an additional NBD_INFO_*
tag for advertising a 64-bit non-data limit down the road.
In fact, maybe we should plan for that right away, by having four
parameters, ending with 'uint32_t *maxdata, uint64_t *maxlen'. This
would match the fact that right now, the blocksize filter allows
maxdata=32M maxlen=3G to break up large pwrite requests differently
than large zero requests. For the existing NBD protocol, only the
first three will be advertised to the client (and we ignore the fourth
for now), but down the road with 64-bit extensions, it will make it
easier to advertise a 64-bit non-data max length alongside the
existing 3 32-bit constraints, as the NBD protocol gains another
extension.
The alternative is that if NBD gains a new NBD_INFO_* tag, we would
have to add a new plugin callback to supply the value for that tag.
I still think that it is nicer to have one parameter for tag we know
about (whether or not we can send the tag to the guest). I can think
of a more extensible interface, shown below, but think it is harder
for plugins fto use correctly, and therefore do not recommend it:
enum nbdkit_size_tag {
NBDKIT_SIZE_TAG_MINIMUM = 0,
NBDKIT_SIZE_TAG_PREFERRED = 1,
NBDKIT_SIZE_TAG_MAXDATA = 2,
NBDKIT_SIZE_TAG_MAXLEN = 3,
};
struct nbdkit_size_constraint_set; /* opaque */
int (*block_size) (nbdkit_next *next, void *handle,
nbdkit_size_constraint_set *set);
int nbdkit_size_constraint_add (nbdkit_size_constraint_set *set,
enum nbdkit_size_tag tag, uint64_t value);
where the plugin would then call something like:
plugin_block_size (...) {
nbdkit_size_constraint_add (set, NBDKIT_SIZE_TAG_MINIMUM, 1);
nbdkit_size_constraint_add (set, NBDKIT_SIZE_TAG_PREFERRED, 64 * 1024);
nbdkit_size_constraint_add (set, NBDKIT_SIZE_TAG_MAXLEN, 1ULL<<40);
}
+++ b/server/backend.c
@@ -214,6 +214,7 @@ static struct nbdkit_next_ops next_ops = {
.finalize = backend_finalize,
.export_description = backend_export_description,
.get_size = backend_get_size,
+ .block_size = backend_block_size,
.can_write = backend_can_write,
.can_flush = backend_can_flush,
.is_rotational = backend_is_rotational,
@@ -265,6 +266,7 @@ backend_open (struct backend *b, int readonly, const char
*exportname,
c->conn = shared ? NULL : conn;
c->state = 0;
c->exportsize = -1;
+ c->minimum_block_size = c->preferred_block_size = c->maximum_block_size = -1;
So we actually implement two sentinels - all -1 (we haven't
initialized yet, and a plugin cannot explicitly set things to this
value, because it violates minimum <= 64k), and all 0 (initialized but
nothing to advertise), in addition to all other values (initialized,
and passed our constraint validator so worth advertising).
+static int
+plugin_block_size (struct context *c,
+ uint32_t *minimum, uint32_t *preferred, uint32_t *maximum)
+{
+ struct backend *b = c->b;
+ struct backend_plugin *p = container_of (b, struct backend_plugin, backend);
+ int r;
+
+ if (p->plugin.block_size) {
+ r = p->plugin.block_size (c->handle, minimum, preferred, maximum);
+ if (r == 0) {
+ /* To make scripting easier, it's permitted to set
+ * minimum = preferred = maximum = 0 and return 0.
+ * That means "no information", and works the same
+ * way as the else clause below.
+ */
+ if (*minimum == 0 && *preferred == 0 && *maximum == 0)
+ return 0;
+
+ if (*minimum < 1 || *minimum > 65536) {
+ nbdkit_error ("plugin must set minimum block size between 1 and
64K");
+ r = -1;
+ }
+ if (! is_power_of_2 (*minimum)) {
+ nbdkit_error ("plugin must set minimum block size to a power of 2");
+ r = -1;
+ }
Did we want to stick all of the constraint validation code in a common
helper function, rather than open-coding it at each place that wants
to perform validation?
Otherwise, this is looking good.
--
Eric Blake, Principal Software Engineer
Red Hat, Inc. +1-919-301-3266
Virtualization:
qemu.org |
libvirt.org