On 01/16/2018 08:40 AM, Richard W.M. Jones wrote:
Here's my second attempt at a filter API.
As before, .config and .config_complete can only call the
next->.config or next->.config_complete methods in the chain
respectively.
The change is with the connected functions (get_size, pread, pwrite,
etc.). Here we pass a struct of next functions allowing the filter to
call any functions on the plugin, so for example a write can turn into
read + write (and vice versa although that might not be a good idea).
A read turning into a read+write makes total sense when doing
copy-on-read thin-provisioning (the read from a remote site moves it by
writing a copy into a local site, so that future reads at that same
address are faster). But yeah, we're trusting that filters will not
abuse expected semantics.
#ifndef NBDKIT_FILTER_H
#define NBDKIT_FILTER_H
/* This header also defines some useful functions like nbdkit_debug
* and nbdkit_parse_size which are appropriate for filters to use.
Too much copy-and-paste?
*/
#include <nbdkit-plugin.h>
I guess you get those useful functions by inclusion, rather than by
direct definition.
struct nbdkit_next {
int64_t (*get_size) (void *nxdata);
int (*can_write) (void *nxdata);
int (*can_flush) (void *nxdata);
int (*is_rotational) (void *nxdata);
int (*can_trim) (void *nxdata);
int (*pread) (void *nxdata, void *buf, uint32_t count, uint64_t offset);
int (*pwrite) (void *nxdata,
const void *buf, uint32_t count, uint64_t offset);
int (*flush) (void *nxdata);
int (*trim) (void *nxdata, uint32_t count, uint64_t offset);
int (*zero) (void *nxdata, uint32_t count, uint64_t offset, int may_trim);
};
While we want to support plugins that use the old API, should we declare
that filters only use the new API? Or does my work to add FUA
passthrough need to be careful on how a filter does the work;
particularly if mixing a filter without FUA knowledge with a backend
that can do it? I guess as long as we stabilize all of the filter and
FUA code into the same nbdkit release, it's okay if we tweak the filter
definition slightly in the next few patches.
struct nbdkit_filter {
/* Do not set these fields directly; use NBDKIT_REGISTER_FILTER.
* They exist so that we can support filters compiled against
* one version of the header with a runtime compiled against a
* different version with more (or fewer) fields.
*/
uint64_t _struct_size;
int _api_version;
/* New fields will only be added at the end of the struct. */
const char *name;
const char *longname;
const char *version;
const char *description;
void (*load) (void);
void (*unload) (void);
int (*config) (nbdkit_next_config *next, void *nxdata,
const char *key, const char *value);
int (*config_complete) (nbdkit_next_config_complete *next, void *nxdata);
const char *config_help;
void * (*open) (int readonly);
Is it conceivable that opening a filter may want to read data from the
underlying backend? Do we have guarantees on lifecycle ordering across
the chain (if the command line uses filter1 filter2 plugin, we open
plugin first, filter2 second, filter1 last; then close in reverse order)?
nbdkit-filter.pod
Different filters can be stacked:
NBD ┌─────────┐ ┌─────────┐ ┌────────┐
client ───▶│ filter1 │───▶│ filter2 │── ─ ─ ──▶│ plugin │
request └─────────┘ └─────────┘ └────────┘
Does conversion from POD to other formats preserve proper formatting of
this ASCII-art?
=head2 C<.name>
const char *name;
This field (a string) is required, and B<must> contain only ASCII
alphanumeric characters and be unique amongst all filters.
Do filters and plugins share the same namespace, or can you have a
filter whose name matches a plugin?
=head2 C<.open>
void * (*open) (int readonly);
This is called when a new client connection is opened and can be used
to allocate any per-connection data structures needed by the filter.
The handle (which is not the same as the plugin handle) is passed back
to other filter callbacks and could be freed in the C<.close>
callback.
Note that the handle is completely opaque to nbdkit, but it must not
be NULL.
If there is an error, C<.open> should call C<nbdkit_error> with an
error message and return C<NULL>.
I guess if you wanted to be truly opaque and support NULL, you'd have to
have something like:
int (*open) (int readonly, void **result)
which populates *result when returning 0, and returns -1 on failure.
But I'm also fine with your choice of stating that the opaque data has
to be anything other than NULL.
In general, this looks like a bit better approach than your first
attempt, in that it lets filters do a lot more during an open connection.
--
Eric Blake, Principal Software Engineer
Red Hat, Inc. +1-919-301-3266
Virtualization:
qemu.org |
libvirt.org