From: "Richard W.M. Jones" <rjones(a)redhat.com>
See also:
https://www.redhat.com/archives/libguestfs/2020-July/msg00090.html
This adds the hooks for filters and plugins; later patches will put
them to use. For now, a filter can only modify export names when a
client requests; a later patch will probably add a way for filters to
open an arbitrary connection on the plugin independent of any
connection from the client, which will give filters much more
flexibility on how they manage export names.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
docs/nbdkit-filter.pod | 92 ++++++++++++++++++++++++++++++++++----
docs/nbdkit-plugin.pod | 64 ++++++++++++++++++++++++--
docs/nbdkit-protocol.pod | 4 +-
include/nbdkit-filter.h | 6 +++
include/nbdkit-plugin.h | 3 ++
server/filters.c | 6 ++-
server/plugins.c | 10 ++++-
tests/test-layers-filter.c | 10 +++++
tests/test-layers-plugin.c | 9 ++++
tests/test-layers.c | 15 +++++++
10 files changed, 202 insertions(+), 17 deletions(-)
diff --git a/docs/nbdkit-filter.pod b/docs/nbdkit-filter.pod
index bb4f0269..12343dbf 100644
--- a/docs/nbdkit-filter.pod
+++ b/docs/nbdkit-filter.pod
@@ -129,15 +129,17 @@ which is required.
F<nbdkit-filter.h> defines some function types (C<nbdkit_next_config>,
C<nbdkit_next_config_complete>, C<nbdkit_next_get_ready>,
C<nbdkit_next_after_fork>, C<nbdkit_next_preconnect>,
-C<nbdkit_next_open>) and a structure called C<struct nbdkit_next_ops>.
-These abstract the next plugin or filter in the chain. There is also
-an opaque pointer C<nxdata> which must be passed along when calling
-these functions. The value of C<nxdata> passed to C<.open> has a
-stable lifetime that lasts to the corresponding C<.close>, with all
-intermediate functions (such as C<.pread>) receiving the same value
-for convenience; the only exceptions where C<nxdata> is not reused are
-C<.config>, C<.config_complete>, C<.get_ready>, C<.after_fork>
and
-C<.preconnect>, which are called outside the lifetime of a connection.
+C<nbdkit_next_list_exports>, C<nbdkit_next_open>) and a structure
+called C<struct nbdkit_next_ops>. These abstract the next plugin or
+filter in the chain. There is also an opaque pointer C<nxdata> which
+must be passed along when calling these functions. The value of
+C<nxdata> passed to C<.open> has a stable lifetime that lasts to the
+corresponding C<.close>, with all intermediate functions (such as
+C<.pread>) receiving the same value for convenience; the only
+exceptions where C<nxdata> is not reused are C<.config>,
+C<.config_complete>, C<.get_ready>, C<.after_fork>,
C<.preconnect> and
+C<.list_exports>, which are called outside the lifetime of a
+connection.
=head2 Next config, open and close
@@ -326,6 +328,78 @@ filter access to the server.
If there is an error, C<.preconnect> should call C<nbdkit_error> with
an error message and return C<-1>.
+=head2 C<.list_exports>
+
+ int (*list_exports) (nbdkit_next_list_exports *next, void *nxdata,
+ int readonly, int default_only,
+ struct nbdkit_exports *exports);
+
+This intercepts the plugin C<.list_exports> method and can be used to
+filter which exports are advertised.
+
+It is possible for filters to transform the exports list received back
+from the layer below. Without error checking it would look like this:
+
+ myfilter_list_exports (...)
+ {
+ size_t i;
+ struct nbdkit_exports *exports2;
+ struct nbdkit_export e;
+ char *name, *desc;
+
+ exports2 = nbdkit_exports_new (default_only);
+ next_list_exports (nxdata, readonly, default_only, exports);
+ for (i = 0; i < nbdkit_exports_count (exports2); ++i) {
+ e = nbdkit_get_export (exports2, i);
+ name = adjust (e.name);
+ desc = adjust (e.desc);
+ nbdkit_add_export (exports, name, desc);
+ free (name);
+ free (desc);
+ }
+ nbdkit_exports_free (exports2);
+ }
+
+If there is an error, C<.list_exports> should call C<nbdkit_error> with
+an error message and return C<-1>.
+
+=head3 Allocating and freeing nbdkit_exports list
+
+Two functions are provided to filters only for allocating and freeing
+the list:
+
+ struct nbdkit_exports *nbdkit_exports_new (int default_only);
+
+Allocates and returns a new, empty exports list. The C<default_only>
+parameter should match whether the list is intended to grab the
+canonical name of the default export, or all exports.
+
+On error this function can return C<NULL>. In this case it calls
+C<nbdkit_error> as required. C<errno> will be set to a suitable
+value.
+
+ void nbdkit_exports_free (struct nbdkit_exports *);
+
+Frees an existing exports list.
+
+=head3 Iterating over nbdkit_exports list
+
+Two functions are provided to filters only to iterate over the exports
+in order:
+
+ size_t nbdkit_exports_count (const struct nbdkit_exports *);
+
+Returns the number of exports in the list.
+
+ struct nbdkit_export {
+ char *name;
+ char *description;
+ };
+ const struct nbdkit_export nbdkit_get_export (const struct nbdkit_exports *,
+ size_t i);
+
+Returns a copy of the C<i>'th export.
+
=head2 C<.open>
void * (*open) (nbdkit_next_open *next, void *nxdata,
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index f8e9962a..9341f282 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -152,6 +152,9 @@ the plugin:
│ preconnect │ │
└──────┬─────┘ │
┌──────┴─────┐ │
+ │list_exports│ │
+ └──────┬─────┘ │
+ ┌──────┴─────┐ │
│ open │ │
└──────┬─────┘ │
┌──────┴─────┐ NBD option │
@@ -160,10 +163,10 @@ the plugin:
┌──────┴─────┐ ┌──────┴─────┐ client #2
│ get_size │ │ preconnect │
└──────┬─────┘ └──────┬─────┘
- ┌──────┴─────┐ data ┌──────┴─────┐
- │ pread │ serving │ open │
- └──────┬─────┘↺ └──────┬─────┘
- ┌──────┴─────┐ ...
+ ┌──────┴─────┐ data
+ │ pread │ serving
+ └──────┬─────┘↺ ...
+ ┌──────┴─────┐
│ pwrite │
└──────┬─────┘↺ ┌──────┴─────┐
┌──────┴─────┐ │ close │
@@ -236,6 +239,12 @@ L<getpid(2)>.
Called when a TCP connection has been made to the server. This
happens early, before NBD or TLS negotiation.
+=item C<.list_exports>
+
+Early in option negotiation the client may try to list the exports
+served by the plugin, and plugins can optionally implement this
+callback to answer the client. See L</EXPORT NAME> below.
+
=item C<.open>
A new client has connected and finished the NBD handshake. TLS
@@ -652,6 +661,53 @@ Returning C<0> will allow the connection to continue. If there
is an
error or you want to deny the connection, call C<nbdkit_error> with an
error message and return C<-1>.
+=head2 C<.list_exports>
+
+ int list_exports (int readonly, int default_only,
+ struct nbdkit_exports *exports);
+
+This optional callback is called if the client tries to list the
+exports served by the plugin (using C<NBD_OPT_LIST>). If the plugin
+does not supply this callback then a single export called C<""> is
+returned. The NBD protocol defines C<""> as the default export, so
+this is suitable for plugins which ignore the export name and always
+serve the same content. See also L</EXPORT NAME> below.
+
+The C<readonly> flag informs the plugin that the server was started
+with the I<-r> flag on the command line, which is the same value
+passed to C<.preconnect> and C<.open>. However, the NBD protocol does
+not yet have a way to let the client advertise an intent to be
+read-only even when the server allows writes, so this parameter may
+not be as useful as it appears.
+
+If the C<default_only> flag is set then the client is querying for the
+name of the default export, and the plugin may optimize by adding only
+a single export to the returned list (the default export name, usually
+C<"">). The plugin can ignore this flag and return all exports if it
+wants.
+
+The C<exports> parameter is an opaque object for collecting the list
+of exports. Call C<nbdkit_add_export> to add a single export to the
+list. If the plugin has a concept of a default export (usually but
+not always called C<"">) then it should return that first in the list.
+
+ int nbdkit_add_export (struct nbdkit_export *exports,
+ const char *name, const char *description);
+
+The C<name> must be a non-NULL, UTF-8 string between 0 and 4096 bytes
+in length. Export names must be unique. C<description> is an
+optional description of the export which some clients can display but
+which is otherwise unused (if you don't want a description, you can
+pass this parameter as C<NULL>). The string(s) are copied into the
+exports list so you may free them immediately after calling this
+function. C<nbdkit_add_export> returns C<0> on success or C<-1> on
+failure; on failure C<nbdkit_error> has already been called, with
+C<errno> set to a suitable value.
+
+Returning C<0> will send the list of exports back to the client. If
+there is an error, C<.list_exports> should call C<nbdkit_error> with
+an error message and return C<-1>.
+
=head2 C<.open>
void *open (int readonly);
diff --git a/docs/nbdkit-protocol.pod b/docs/nbdkit-protocol.pod
index 8fe8c67e..b923d367 100644
--- a/docs/nbdkit-protocol.pod
+++ b/docs/nbdkit-protocol.pod
@@ -104,7 +104,9 @@ read the client export name added in nbdkit E<ge> 1.15.2.
Versions of nbdkit before 1.16 could advertise a single export name to
clients, via a now deprecated side effect of the I<-e> option. In nbdkit
1.15.2, plugins could read the client requested export name using
-C<nbdkit_export_name()> and serve different content.
+C<nbdkit_export_name()> and serve different content. In nbdkit
+1.21.22, plugins could implement C<.list_exports> to answer
+C<NBD_OPT_LIST> queries.
=item C<NBD_FLAG_NO_ZEROES>
diff --git a/include/nbdkit-filter.h b/include/nbdkit-filter.h
index 01ff3dce..08c6abc4 100644
--- a/include/nbdkit-filter.h
+++ b/include/nbdkit-filter.h
@@ -65,6 +65,9 @@ typedef int nbdkit_next_config_complete (nbdkit_backend *nxdata);
typedef int nbdkit_next_get_ready (nbdkit_backend *nxdata);
typedef int nbdkit_next_after_fork (nbdkit_backend *nxdata);
typedef int nbdkit_next_preconnect (nbdkit_backend *nxdata, int readonly);
+typedef int nbdkit_next_list_exports (nbdkit_backend *nxdata, int readonly,
+ int default_only,
+ struct nbdkit_exports *exports);
typedef int nbdkit_next_open (nbdkit_backend *nxdata,
int readonly, const char *exportname);
@@ -167,6 +170,9 @@ struct nbdkit_filter {
int (*after_fork) (nbdkit_next_after_fork *next, nbdkit_backend *nxdata);
int (*preconnect) (nbdkit_next_preconnect *next, nbdkit_backend *nxdata,
int readonly);
+ int (*list_exports) (nbdkit_next_list_exports *next, nbdkit_backend *nxdata,
+ int readonly, int default_only,
+ struct nbdkit_exports *exports);
void * (*open) (nbdkit_next_open *next, nbdkit_backend *nxdata,
int readonly, const char *exportname);
diff --git a/include/nbdkit-plugin.h b/include/nbdkit-plugin.h
index c6c1256c..86b8565d 100644
--- a/include/nbdkit-plugin.h
+++ b/include/nbdkit-plugin.h
@@ -139,6 +139,9 @@ struct nbdkit_plugin {
int (*get_ready) (void);
int (*after_fork) (void);
+
+ int (*list_exports) (int readonly, int default_only,
+ struct nbdkit_exports *exports);
};
extern void nbdkit_set_error (int err);
diff --git a/server/filters.c b/server/filters.c
index e5b5b860..20518354 100644
--- a/server/filters.c
+++ b/server/filters.c
@@ -241,7 +241,11 @@ static int
filter_list_exports (struct backend *b, int readonly, int default_only,
struct nbdkit_exports *exports)
{
- /* XXX No filter override yet... */
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ if (f->filter.list_exports)
+ return f->filter.list_exports (backend_list_exports, b->next,
+ readonly, default_only, exports);
return backend_list_exports (b->next, readonly, default_only, exports);
}
diff --git a/server/plugins.c b/server/plugins.c
index 8020046b..d4364cd2 100644
--- a/server/plugins.c
+++ b/server/plugins.c
@@ -161,6 +161,7 @@ plugin_dump_fields (struct backend *b)
HAS (get_ready);
HAS (after_fork);
HAS (preconnect);
+ HAS (list_exports);
HAS (open);
HAS (close);
HAS (get_size);
@@ -281,8 +282,13 @@ static int
plugin_list_exports (struct backend *b, int readonly, int default_only,
struct nbdkit_exports *exports)
{
- /* XXX No plugin support yet, so for now just advertise "" */
- return nbdkit_add_export (exports, "", NULL);
+ GET_CONN;
+ struct backend_plugin *p = container_of (b, struct backend_plugin, backend);
+
+ if (!p->plugin.list_exports)
+ return nbdkit_add_export (exports, "", NULL);
+
+ return p->plugin.list_exports (readonly, default_only, exports);
}
static void *
diff --git a/tests/test-layers-filter.c b/tests/test-layers-filter.c
index 29d8b669..397af575 100644
--- a/tests/test-layers-filter.c
+++ b/tests/test-layers-filter.c
@@ -106,6 +106,15 @@ test_layers_filter_preconnect (nbdkit_next_preconnect *next,
return next (nxdata, readonly);
}
+static int
+test_layers_filter_list_exports (nbdkit_next_list_exports *next, void *nxdata,
+ int readonly, int default_only,
+ struct nbdkit_exports *exports)
+{
+ DEBUG_FUNCTION;
+ return next (nxdata, readonly, default_only, exports);
+}
+
static void *
test_layers_filter_open (nbdkit_next_open *next, void *nxdata,
int readonly, const char *exportname)
@@ -370,6 +379,7 @@ static struct nbdkit_filter filter = {
.get_ready = test_layers_filter_get_ready,
.after_fork = test_layers_filter_after_fork,
.preconnect = test_layers_filter_preconnect,
+ .list_exports = test_layers_filter_list_exports,
.open = test_layers_filter_open,
.close = test_layers_filter_close,
.prepare = test_layers_filter_prepare,
diff --git a/tests/test-layers-plugin.c b/tests/test-layers-plugin.c
index ccb67fe4..1dfd069e 100644
--- a/tests/test-layers-plugin.c
+++ b/tests/test-layers-plugin.c
@@ -93,6 +93,14 @@ test_layers_plugin_preconnect (int readonly)
return 0;
}
+static int
+test_layers_plugin_list_exports (int readonly, int default_only,
+ struct nbdkit_exports *exports)
+{
+ DEBUG_FUNCTION;
+ return nbdkit_add_export (exports, "", NULL);
+}
+
static void *
test_layers_plugin_open (int readonly)
{
@@ -248,6 +256,7 @@ static struct nbdkit_plugin plugin = {
.get_ready = test_layers_plugin_get_ready,
.after_fork = test_layers_plugin_after_fork,
.preconnect = test_layers_plugin_preconnect,
+ .list_exports = test_layers_plugin_list_exports,
.open = test_layers_plugin_open,
.close = test_layers_plugin_close,
.get_size = test_layers_plugin_get_size,
diff --git a/tests/test-layers.c b/tests/test-layers.c
index 9a0279f1..dd826f36 100644
--- a/tests/test-layers.c
+++ b/tests/test-layers.c
@@ -326,6 +326,21 @@ main (int argc, char *argv[])
"test_layers_plugin_preconnect",
NULL);
+ /* list_exports methods called in outer-to-inner order, complete
+ * in inner-to-outer order. But since we didn't send NBD_OPT_LIST,
+ * the outer filter does not expose a list; rather, the rest of the
+ * chain is used to resolve the canonical name of the default export.
+ */
+ log_verify_seen_in_order
+ ("filter3: test_layers_filter_list_exports",
+ "testlayersfilter2: list_exports",
+ "filter2: test_layers_filter_list_exports",
+ "testlayersfilter1: list_exports",
+ "filter1: test_layers_filter_list_exports",
+ "testlayersplugin: list_exports",
+ "test_layers_plugin_list_exports",
+ NULL);
+
/* open methods called in outer-to-inner order, but thanks to next
* pointer, complete in inner-to-outer order. */
log_verify_seen_in_order
--
2.28.0