This allows plugins (or filters) to read the export name which was
passed to the server from the client.
---
TODO | 8 +++++++
docs/nbdkit-plugin.pod | 29 ++++++++++++++++++++++
include/nbdkit-common.h | 1 +
server/connections.c | 36 ++++++++++++++++++----------
server/internal.h | 1 +
server/nbdkit.syms | 1 +
server/protocol-handshake-newstyle.c | 31 ++++++++++++++----------
server/public.c | 13 ++++++++++
8 files changed, 95 insertions(+), 25 deletions(-)
diff --git a/TODO b/TODO
index 49b60b1..2468d74 100644
--- a/TODO
+++ b/TODO
@@ -62,6 +62,12 @@ General ideas for improvements
and also look at the implementation of the -swap option in
nbd-client.
+* Clients should be able to list export names supported by plugins.
+ Current behaviour is not really correct: We only list the -e
+ parameter from the command line, which is different from the export
+ name(s) that a plugin might want to support. Probably we should
+ deprecate the -e option entirely since it does nothing useful.
+
Suggestions for plugins
-----------------------
@@ -190,3 +196,5 @@ using ‘#define NBDKIT_API_VERSION <version>’.
value) strings. nbdkit should know the possible keys for the plugin
and filters, and the type of the values, and both check and parse
them for the plugin.
+
+* Modify open() API so it takes an export name parameter.
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index bb162e4..219906e 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -362,6 +362,32 @@ requested, or -1 after calling C<nbdkit_error> if there is no
point in
continuing the current command. Attempts to sleep more than
C<INT_MAX> seconds are treated as an error.
+=head1 EXPORT NAME
+
+If the client negotiated an NBD export name with nbdkit then plugins
+may read this from any connected callbacks. Nbdkit's normal behaviour
+is to accept any export name passed by the client, log it in debug
+output, but otherwise ignore it. By using C<nbdkit_export_name>
+plugins may choose to filter by export name or serve different
+content.
+
+=head2 C<nbdkit_export_name>
+
+ const char *nbdkit_export_name (void);
+
+Return the optional NBD export name if one was negotiated with the
+current client (this uses thread-local magic so no parameter is
+required). The returned string is only valid while the client is
+connected, so if you need to store it in the plugin you must copy it.
+
+The export name is a free-form text string, it is not necessarily a
+path or filename and it does not need to begin with a C<'/'>
+character. The NBD protocol describes the empty string (C<"">) as a
+representing a "default export" or to be used in cases where the
+export name does not make sense.
+
+On error, nbdkit_error is called and the call returns C<NULL>.
+
=head1 CALLBACKS
=head2 C<.name>
@@ -709,6 +735,9 @@ request or setting C<NBDKIT_FLAG_FUA> on a request must be
visible
across all connections to the plugin before the plugin replies to that
request.
+Properly working clients should send the same export name for each of
+these connections.
+
If you use Linux L<nbd-client(8)> option S<I<-C num>> with
S<num E<gt> 1> then Linux checks this flag and will refuse to connect
if C<.can_multi_conn> is false.
diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h
index 42d94a1..acf0abd 100644
--- a/include/nbdkit-common.h
+++ b/include/nbdkit-common.h
@@ -86,6 +86,7 @@ extern int nbdkit_parse_bool (const char *str);
extern int nbdkit_read_password (const char *value, char **password);
extern char *nbdkit_realpath (const char *path);
extern int nbdkit_nanosleep (unsigned sec, unsigned nsec);
+extern const char *nbdkit_export_name (void);
struct nbdkit_extents;
extern int nbdkit_add_extent (struct nbdkit_extents *,
diff --git a/server/connections.c b/server/connections.c
index 8ef5e57..819f7b8 100644
--- a/server/connections.c
+++ b/server/connections.c
@@ -255,11 +255,18 @@ new_connection (int sockin, int sockout, int nworkers)
perror ("malloc");
return NULL;
}
+
+ conn->status_pipe[0] = conn->status_pipe[1] = -1;
+
+ conn->exportname = strdup ("");
+ if (conn->exportname == NULL) {
+ perror ("strdup");
+ goto error;
+ }
conn->handles = calloc (backend->i + 1, sizeof *conn->handles);
if (conn->handles == NULL) {
perror ("malloc");
- free (conn);
- return NULL;
+ goto error;
}
conn->nr_handles = backend->i + 1;
memset (conn->handles, -1, conn->nr_handles * sizeof *conn->handles);
@@ -272,8 +279,7 @@ new_connection (int sockin, int sockout, int nworkers)
#ifdef HAVE_PIPE2
if (pipe2 (conn->status_pipe, O_NONBLOCK | O_CLOEXEC)) {
perror ("pipe2");
- free (conn);
- return NULL;
+ goto error;
}
#else
/* If we were fully parallel, then this function could be
@@ -289,29 +295,24 @@ new_connection (int sockin, int sockout, int nworkers)
lock_request (NULL);
if (pipe (conn->status_pipe)) {
perror ("pipe");
- free (conn);
unlock_request (NULL);
- return NULL;
+ goto error;
}
if (set_nonblock (set_cloexec (conn->status_pipe[0])) == -1) {
perror ("fcntl");
close (conn->status_pipe[1]);
- free (conn);
unlock_request (NULL);
- return NULL;
+ goto error;
}
if (set_nonblock (set_cloexec (conn->status_pipe[1])) == -1) {
perror ("fcntl");
close (conn->status_pipe[0]);
- free (conn);
unlock_request (NULL);
- return NULL;
+ goto error;
}
unlock_request (NULL);
#endif
}
- else
- conn->status_pipe[0] = conn->status_pipe[1] = -1;
conn->sockin = sockin;
conn->sockout = sockout;
pthread_mutex_init (&conn->request_lock, NULL);
@@ -329,6 +330,16 @@ new_connection (int sockin, int sockout, int nworkers)
threadlocal_set_conn (conn);
return conn;
+
+ error:
+ if (conn->status_pipe[0] >= 0)
+ close (conn->status_pipe[0]);
+ if (conn->status_pipe[1] >= 0)
+ close (conn->status_pipe[1]);
+ free (conn->exportname);
+ free (conn->handles);
+ free (conn);
+ return NULL;
}
static void
@@ -373,6 +384,7 @@ free_connection (struct connection *conn)
pthread_mutex_destroy (&conn->status_lock);
free (conn->handles);
+ free (conn->exportname);
free (conn);
}
diff --git a/server/internal.h b/server/internal.h
index ac5b894..1f72b01 100644
--- a/server/internal.h
+++ b/server/internal.h
@@ -181,6 +181,7 @@ struct connection {
struct b_conn_handle *handles;
size_t nr_handles;
+ char *exportname;
uint32_t cflags;
uint16_t eflags;
bool using_tls;
diff --git a/server/nbdkit.syms b/server/nbdkit.syms
index 2a024ed..1fb1315 100644
--- a/server/nbdkit.syms
+++ b/server/nbdkit.syms
@@ -42,6 +42,7 @@
nbdkit_add_extent;
nbdkit_debug;
nbdkit_error;
+ nbdkit_export_name;
nbdkit_extents_count;
nbdkit_extents_free;
nbdkit_extents_new;
diff --git a/server/protocol-handshake-newstyle.c b/server/protocol-handshake-newstyle.c
index 75465b7..87e0bcd 100644
--- a/server/protocol-handshake-newstyle.c
+++ b/server/protocol-handshake-newstyle.c
@@ -272,11 +272,17 @@ negotiate_handshake_newstyle_options (struct connection *conn)
if (conn_recv_full (conn, data, optlen,
"read: %s: %m", name_of_nbd_opt (option)) == -1)
return -1;
- /* Apart from printing it, ignore the export name. */
+ /* Print the export name and save it in the connection. */
data[optlen] = '\0';
- debug ("newstyle negotiation: %s: "
- "client requested export '%s' (ignored)",
+ debug ("newstyle negotiation: %s: client requested export '%s'",
name_of_nbd_opt (option), data);
+ free (conn->exportname);
+ conn->exportname = malloc (optlen+1);
+ if (conn->exportname == NULL) {
+ nbdkit_error ("malloc: %m");
+ return -1;
+ }
+ strcpy (conn->exportname, data);
/* We have to finish the handshake by sending handshake_finish. */
if (finish_newstyle_options (conn, &exportsize) == -1)
@@ -388,7 +394,6 @@ negotiate_handshake_newstyle_options (struct connection *conn)
uint16_t nrinfos;
uint16_t info;
size_t i;
- CLEANUP_FREE char *requested_exportname = NULL;
/* Validate the name length and number of INFO requests. */
memcpy (&exportnamelen, &data[0], 4);
@@ -411,19 +416,19 @@ negotiate_handshake_newstyle_options (struct connection *conn)
continue;
}
- /* As with NBD_OPT_EXPORT_NAME we print the export name and then
- * ignore it.
+ /* As with NBD_OPT_EXPORT_NAME we print the export name and
+ * save it in the connection.
*/
- requested_exportname = malloc (exportnamelen+1);
- if (requested_exportname == NULL) {
+ free (conn->exportname);
+ conn->exportname = malloc (exportnamelen+1);
+ if (conn->exportname == NULL) {
nbdkit_error ("malloc: %m");
return -1;
}
- memcpy (requested_exportname, &data[4], exportnamelen);
- requested_exportname[exportnamelen] = '\0';
- debug ("newstyle negotiation: %s: "
- "client requested export '%s' (ignored)",
- optname, requested_exportname);
+ memcpy (conn->exportname, &data[4], exportnamelen);
+ conn->exportname[exportnamelen] = '\0';
+ debug ("newstyle negotiation: %s: client requested export
'%s'",
+ optname, conn->exportname);
/* The spec is confusing, but it is required that we send back
* NBD_INFO_EXPORT, even if the client did not request it!
diff --git a/server/public.c b/server/public.c
index 630de9b..96ab353 100644
--- a/server/public.c
+++ b/server/public.c
@@ -379,3 +379,16 @@ nbdkit_nanosleep (unsigned sec, unsigned nsec)
return 0;
#endif
}
+
+const char *
+nbdkit_export_name (void)
+{
+ struct connection *conn = threadlocal_get_conn ();
+
+ if (!conn) {
+ nbdkit_error ("no connection in this thread");
+ return NULL;
+ }
+
+ return conn->exportname;
+}
--
2.23.0