[Libguestfs] [PATCH nbdkit] server: Pass the export name through filter .open calls.

Eric Blake eblake at redhat.com
Wed Jul 22 00:41:21 UTC 2020


On 7/21/20 3:47 PM, Richard W.M. Jones wrote:
> To allow filters to modify the export name as it passes through the
> layers this commit makes several changes:
> 
> The filter .open callback now takes an extra parameter, the export
> name.  This is always non-NULL (for oldstyle it is "").  This string
> has a short lifetime and filters that need to hang on to it must take
> a copy.  The filter must pass the exportname parameter down to the
> next layer (or potentially modify it).
> 
> The plugin .open callback is unchanged, but the binding to it in
> server/plugins.c takes a copy of the exportname in the handle and this
> is what nbdkit_export_name returns to plugins.  Eventually we will
> deprecate that function and the V3 .open callback in plugins will have
> the extra parameter, but this is not implemented yet.
> 
> nbdkit_export_name can only be called from plugins, not filters.
> Filters should use the .open(...exportname) parameter if they need the
> export name.
> 
> Filter .reopen also takes the exportname.  The only filter that uses
> it (retry) must save the exportname from .open in order to pass it to
> .reopen.  This filter already does the same for the readonly flag so
> this seems reasonable.
> 
> The handling of NBD_OPT_SET_META_CONTEXT interacted with the export
> name (see commit 20b8509a9ccdab118ce7b7043be63bbad74f1e79).  I have
> attempted to keep previous behaviour in this change, but note that
> there is no regression test for this.  I added a debug message so we
> can tell when this unusual case actually happens which should help
> with diagnosis of problems.

Yeah, that commit specifically mentioned that I used gdb breakpoints in 
qemu-io to test things, and was not interested in hacking libnbd at the 
time.  Although now that we are debating about exposing nbd_opt_* 
commands in libnbd to someone that opts in, maybe that _could_ be a case 
to write such a regression test.  Meanwhile, I'll just try to fire up 
another gdb session to prove to myself that things still work.

> 
> All of the above is internal refactoring and should have no visible
> external effect on server behaviour or how plugins are written.
> Filters need to be updated as discussed above.  All the in-tree
> filters have been, and we assume there are no out of tree filters
> because of the unstable filter API.

And even if there are, whoever maintains it can get a good idea of what 
to do by reading this patch ;)

> 
> See also this thread:
> https://www.redhat.com/archives/libguestfs/2020-July/thread.html#00087
> ---

> +++ b/server/internal.h
> @@ -246,8 +246,6 @@ struct connection {
>     struct handle *handles;       /* One per plugin and filter. */
>     size_t nr_handles;
>   
> -  char exportname[NBD_MAX_STRING + 1];
> -  uint32_t exportnamelen;
>     uint32_t cflags;
>     uint16_t eflags;
>     bool handshake_complete;
> @@ -255,6 +253,9 @@ struct connection {
>     bool structured_replies;
>     bool meta_context_base_allocation;
>   
> +  char *exportname_from_set_meta_context;
> +  char *exportname;

Interesting switch from array to pointer, but it should work.

> +++ b/server/plugins.c
> @@ -278,12 +278,30 @@ plugin_preconnect (struct backend *b, int readonly)
>   }
>   
>   static void *
> -plugin_open (struct backend *b, int readonly)
> +plugin_open (struct backend *b, int readonly, const char *exportname)
>   {
> +  GET_CONN;
>     struct backend_plugin *p = container_of (b, struct backend_plugin, backend);
>   
>     assert (p->plugin.open != NULL);
>   
> +  /* Save the exportname since the lifetime of the string passed in
> +   * here is likely to be brief.  In addition this provides a place
> +   * for nbdkit_export_name to retrieve it if called from the plugin.
> +   *
> +   * In API V3 we propose to pass the exportname as an extra parameter
> +   * to the (new) plugin.open and deprecate nbdkit_export_name for V3
> +   * users.  Even then we will still need to save it in the handle
> +   * because of the lifetime issue.
> +   */
> +  if (conn->exportname == NULL) {

Can't we assert(!conn->exportname) at this point?  After all, we only 
ever call .open at most once per connection.

> +++ b/server/protocol-handshake-newstyle.c
> @@ -200,11 +200,29 @@ conn_recv_full (void *buf, size_t len, const char *fmt, ...)
>    * in that function, and must not cause any wire traffic.
>    */
>   static int
> -finish_newstyle_options (uint64_t *exportsize)
> +finish_newstyle_options (uint64_t *exportsize,
> +                         const char *exportname_in, uint32_t exportnamelen)
>   {
>     GET_CONN;
>   
> -  if (protocol_common_open (exportsize, &conn->eflags) == -1)
> +  /* Since the exportname string passed here comes directly out of the
> +   * NBD protocol make a temporary copy of the exportname into a
> +   * \0-terminated buffer.
> +   */
> +  CLEANUP_FREE char *exportname = strndup (exportname_in, exportnamelen);
> +  if (exportname == NULL) {
> +    nbdkit_error ("strndup: %m");
> +    return -1;
> +  }
> +
> +  if (conn->exportname_from_set_meta_context &&
> +      strcmp (conn->exportname_from_set_meta_context, exportname) != 0) {
> +    debug ("newstyle negotiation: NBD_OPT_SET_META_CONTEXT export name \"%s\" ≠ final client exportname \"%s\", so discarding the previous context",

Long line, and I don't know if we use UTF-8 in other debug messages or 
should stick to straight ascii.  Compilation may have a glitch if 
compiled under a different locale than the end binary runs in, but these 
days, it's uncommon to find someone running in a single-byte locale 
instead of UTF-8.

> +++ b/filters/ext2/ext2.c

>   /* Create the per-connection handle. */
>   static void *
> -ext2_open (nbdkit_next_open *next, void *nxdata, int readonly)
> +ext2_open (nbdkit_next_open *next, void *nxdata,
> +           int readonly, const char *exportname)

> +
> +  /* If file == NULL (ie. using exportname) then don't
> +   * pass the client exportname to the lower layers.
> +   */
> +  exportname = file ? exportname : "";
> +
> +  /* Request write access to the underlying plugin, for journal replay. */
> +  if (next (nxdata, 0, exportname) == -1) {

Nice demonstration of when the filter changes things vs. does 
pass-through, and will be easy enough to copy into the tar filter.

> +    free (h->exportname);
> +    free (h);
> +    return NULL;
> +  }
> +
>     return h;
>   }
>   
> @@ -148,7 +166,7 @@ ext2_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle,
>     struct ext2_inode inode;
>     int64_t r;
>     CLEANUP_FREE char *name = NULL;
> -  const char *fname = file ?: nbdkit_export_name ();
> +  const char *fname = file ?: h->exportname;

Hmm - we already pass the same 'readonly' state to filter's .prepare, 
but not to backend_prepare(), which has to reconstruct it.  Would it be 
easier to also change the signature of backend_prepare() to take both 
the original readonly and exportname passed to backend_open(), rather 
than making the filter have to save it off in the filter?  It looks like 
protocol-handshake.c is the only caller, and still has everything in 
scope at the time.


> +++ b/filters/log/log.c
> @@ -227,11 +227,12 @@ output_return (struct handle *h, const char *act, uint64_t id, int r, int *err)
>   
>   /* Open a connection. */
>   static void *
> -log_open (nbdkit_next_open *next, void *nxdata, int readonly)
> +log_open (nbdkit_next_open *next, void *nxdata,
> +          int readonly, const char *exportname)
>   {
>     struct handle *h;
>   
> -  if (next (nxdata, readonly) == -1)
> +  if (next (nxdata, readonly, exportname) == -1)
>       return NULL;

Pre-existing - the log filter should include the exportname somewhere in 
its output log.  Well, nothing like the present to fix it ;)

> +++ b/filters/retry/retry.c
> @@ -106,16 +106,18 @@ retry_config (nbdkit_next_config *next, void *nxdata,
>   
>   struct retry_handle {
>     int readonly;                 /* Save original readonly setting. */
> +  const char *exportname;       /* Client exportname. */
>     unsigned reopens;
>     bool open;
>   };
>   
>   static void *
> -retry_open (nbdkit_next_open *next, void *nxdata, int readonly)
> +retry_open (nbdkit_next_open *next, void *nxdata,
> +            int readonly, const char *exportname)
>   {
>     struct retry_handle *h;
>   
> -  if (next (nxdata, readonly) == -1)
> +  if (next (nxdata, readonly, exportname) == -1)
>       return NULL;
>   
>     h = malloc (sizeof *h);
> @@ -125,6 +127,12 @@ retry_open (nbdkit_next_open *next, void *nxdata, int readonly)
>     }
>   
>     h->readonly = readonly;
> +  h->exportname = strdup (exportname);

While I think we can avoid the strdup in the ext2 filter, I agree that 
the retry filter does have to store it.

Otherwise looking good.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org




More information about the Libguestfs mailing list