aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--block/nbd-client.c9
-rw-r--r--blockdev-nbd.c10
-rw-r--r--include/block/nbd.h31
-rw-r--r--nbd/client.c761
-rw-r--r--nbd/server.c24
-rw-r--r--nbd/trace-events17
-rw-r--r--qemu-nbd.c224
-rw-r--r--qemu-nbd.texi119
-rwxr-xr-xscripts/texi2pod.pl2
-rwxr-xr-xtests/qemu-iotests/2232
-rw-r--r--tests/qemu-iotests/223.out20
-rwxr-xr-xtests/qemu-iotests/23330
-rw-r--r--tests/qemu-iotests/233.out19
14 files changed, 938 insertions, 332 deletions
diff --git a/Makefile b/Makefile
index c9bdb67..4219624 100644
--- a/Makefile
+++ b/Makefile
@@ -860,6 +860,8 @@ docs/interop/qemu-qmp-ref.dvi docs/interop/qemu-qmp-ref.html \
docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7: \
docs/interop/qemu-qmp-ref.texi docs/interop/qemu-qmp-qapi.texi
+$(filter %.1 %.7 %.8,$(DOCS)): scripts/texi2pod.pl
+
# Reports/Analysis
%/coverage-report.html:
diff --git a/block/nbd-client.c b/block/nbd-client.c
index ef32075..8135396 100644
--- a/block/nbd-client.c
+++ b/block/nbd-client.c
@@ -249,11 +249,11 @@ static int nbd_parse_blockstatus_payload(NBDClientSession *client,
}
context_id = payload_advance32(&payload);
- if (client->info.meta_base_allocation_id != context_id) {
+ if (client->info.context_id != context_id) {
error_setg(errp, "Protocol error: unexpected context id %d for "
"NBD_REPLY_TYPE_BLOCK_STATUS, when negotiated context "
"id is %d", context_id,
- client->info.meta_base_allocation_id);
+ client->info.context_id);
return -EINVAL;
}
@@ -999,10 +999,11 @@ int nbd_client_init(BlockDriverState *bs,
client->info.structured_reply = true;
client->info.base_allocation = true;
client->info.x_dirty_bitmap = g_strdup(x_dirty_bitmap);
- ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
- tlscreds, hostname,
+ client->info.name = g_strdup(export ?: "");
+ ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), tlscreds, hostname,
&client->ioc, &client->info, errp);
g_free(client->info.x_dirty_bitmap);
+ g_free(client->info.name);
if (ret < 0) {
logout("Failed to negotiate with the NBD server\n");
return ret;
diff --git a/blockdev-nbd.c b/blockdev-nbd.c
index c76d541..d73ac1b 100644
--- a/blockdev-nbd.c
+++ b/blockdev-nbd.c
@@ -146,6 +146,7 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
BlockDriverState *bs = NULL;
BlockBackend *on_eject_blk;
NBDExport *exp;
+ int64_t len;
if (!nbd_server) {
error_setg(errp, "NBD server not running");
@@ -168,6 +169,13 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
return;
}
+ len = bdrv_getlength(bs);
+ if (len < 0) {
+ error_setg_errno(errp, -len,
+ "Failed to determine the NBD export's length");
+ return;
+ }
+
if (!has_writable) {
writable = false;
}
@@ -175,7 +183,7 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
writable = false;
}
- exp = nbd_export_new(bs, 0, -1, name, NULL, bitmap,
+ exp = nbd_export_new(bs, 0, len, name, NULL, bitmap,
writable ? 0 : NBD_FLAG_READ_ONLY,
NULL, false, on_eject_blk, errp);
if (!exp) {
diff --git a/include/block/nbd.h b/include/block/nbd.h
index 1971b55..4faf394 100644
--- a/include/block/nbd.h
+++ b/include/block/nbd.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016-2017 Red Hat, Inc.
+ * Copyright (C) 2016-2019 Red Hat, Inc.
* Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws>
*
* Network Block Device
@@ -263,26 +263,39 @@ struct NBDExportInfo {
bool request_sizes;
char *x_dirty_bitmap;
+ /* Set by client before nbd_receive_negotiate(), or by server results
+ * during nbd_receive_export_list() */
+ char *name; /* must be non-NULL */
+
/* In-out fields, set by client before nbd_receive_negotiate() and
* updated by server results during nbd_receive_negotiate() */
bool structured_reply;
bool base_allocation; /* base:allocation context for NBD_CMD_BLOCK_STATUS */
- /* Set by server results during nbd_receive_negotiate() */
+ /* Set by server results during nbd_receive_negotiate() and
+ * nbd_receive_export_list() */
uint64_t size;
uint16_t flags;
uint32_t min_block;
uint32_t opt_block;
uint32_t max_block;
- uint32_t meta_base_allocation_id;
+ uint32_t context_id;
+
+ /* Set by server results during nbd_receive_export_list() */
+ char *description;
+ int n_contexts;
+ char **contexts;
};
typedef struct NBDExportInfo NBDExportInfo;
-int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
- QCryptoTLSCreds *tlscreds, const char *hostname,
- QIOChannel **outioc, NBDExportInfo *info,
- Error **errp);
+int nbd_receive_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
+ const char *hostname, QIOChannel **outioc,
+ NBDExportInfo *info, Error **errp);
+void nbd_free_export_list(NBDExportInfo *info, int count);
+int nbd_receive_export_list(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
+ const char *hostname, NBDExportInfo **info,
+ Error **errp);
int nbd_init(int fd, QIOChannelSocket *sioc, NBDExportInfo *info,
Error **errp);
int nbd_send_request(QIOChannel *ioc, NBDRequest *request);
@@ -294,8 +307,8 @@ int nbd_errno_to_system_errno(int err);
typedef struct NBDExport NBDExport;
typedef struct NBDClient NBDClient;
-NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
- const char *name, const char *description,
+NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
+ uint64_t size, const char *name, const char *desc,
const char *bitmap, uint16_t nbdflags,
void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp);
diff --git a/nbd/client.c b/nbd/client.c
index f625c20..8a083c2 100644
--- a/nbd/client.c
+++ b/nbd/client.c
@@ -21,6 +21,7 @@
#include "qapi/error.h"
#include "trace.h"
#include "nbd-internal.h"
+#include "qemu/cutils.h"
/* Definitions for opaque data types */
@@ -234,18 +235,24 @@ static int nbd_handle_reply_err(QIOChannel *ioc, NBDOptionReply *reply,
return result;
}
-/* Process another portion of the NBD_OPT_LIST reply. Set *@match if
- * the current reply matches @want or if the server does not support
- * NBD_OPT_LIST, otherwise leave @match alone. Return 0 if iteration
- * is complete, positive if more replies are expected, or negative
- * with @errp set if an unrecoverable error occurred. */
-static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
+/* nbd_receive_list:
+ * Process another portion of the NBD_OPT_LIST reply, populating any
+ * name received into *@name. If @description is non-NULL, and the
+ * server provided a description, that is also populated. The caller
+ * must eventually call g_free() on success.
+ * Returns 1 if name and description were set and iteration must continue,
+ * 0 if iteration is complete (including if OPT_LIST unsupported),
+ * -1 with @errp set if an unrecoverable error occurred.
+ */
+static int nbd_receive_list(QIOChannel *ioc, char **name, char **description,
Error **errp)
{
+ int ret = -1;
NBDOptionReply reply;
uint32_t len;
uint32_t namelen;
- char name[NBD_MAX_NAME_SIZE + 1];
+ char *local_name = NULL;
+ char *local_desc = NULL;
int error;
if (nbd_receive_option_reply(ioc, NBD_OPT_LIST, &reply, errp) < 0) {
@@ -253,9 +260,6 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
}
error = nbd_handle_reply_err(ioc, &reply, errp);
if (error <= 0) {
- /* The server did not support NBD_OPT_LIST, so set *match on
- * the assumption that any name will be accepted. */
- *match = true;
return error;
}
len = reply.length;
@@ -292,45 +296,54 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
nbd_send_opt_abort(ioc);
return -1;
}
- if (namelen != strlen(want)) {
- if (nbd_drop(ioc, len, errp) < 0) {
- error_prepend(errp,
- "failed to skip export name with wrong length: ");
- nbd_send_opt_abort(ioc);
- return -1;
- }
- return 1;
- }
- assert(namelen < sizeof(name));
- if (nbd_read(ioc, name, namelen, errp) < 0) {
+ local_name = g_malloc(namelen + 1);
+ if (nbd_read(ioc, local_name, namelen, errp) < 0) {
error_prepend(errp, "failed to read export name: ");
nbd_send_opt_abort(ioc);
- return -1;
+ goto out;
}
- name[namelen] = '\0';
+ local_name[namelen] = '\0';
len -= namelen;
- if (nbd_drop(ioc, len, errp) < 0) {
- error_prepend(errp, "failed to read export description: ");
- nbd_send_opt_abort(ioc);
- return -1;
+ if (len) {
+ local_desc = g_malloc(len + 1);
+ if (nbd_read(ioc, local_desc, len, errp) < 0) {
+ error_prepend(errp, "failed to read export description: ");
+ nbd_send_opt_abort(ioc);
+ goto out;
+ }
+ local_desc[len] = '\0';
}
- if (!strcmp(name, want)) {
- *match = true;
+
+ trace_nbd_receive_list(local_name, local_desc ?: "");
+ *name = local_name;
+ local_name = NULL;
+ if (description) {
+ *description = local_desc;
+ local_desc = NULL;
}
- return 1;
+ ret = 1;
+
+ out:
+ g_free(local_name);
+ g_free(local_desc);
+ return ret;
}
-/* Returns -1 if NBD_OPT_GO proves the export @wantname cannot be
- * used, 0 if NBD_OPT_GO is unsupported (fall back to NBD_OPT_LIST and
+/*
+ * nbd_opt_info_or_go:
+ * Send option for NBD_OPT_INFO or NBD_OPT_GO and parse the reply.
+ * Returns -1 if the option proves the export @info->name cannot be
+ * used, 0 if the option is unsupported (fall back to NBD_OPT_LIST and
* NBD_OPT_EXPORT_NAME in that case), and > 0 if the export is good to
- * go (with @info populated). */
-static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
- NBDExportInfo *info, Error **errp)
+ * go (with the rest of @info populated).
+ */
+static int nbd_opt_info_or_go(QIOChannel *ioc, uint32_t opt,
+ NBDExportInfo *info, Error **errp)
{
NBDOptionReply reply;
- uint32_t len = strlen(wantname);
+ uint32_t len = strlen(info->name);
uint16_t type;
int error;
char *buf;
@@ -340,16 +353,17 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
* flags still 0 is a witness of a broken server. */
info->flags = 0;
- trace_nbd_opt_go_start(wantname);
+ assert(opt == NBD_OPT_GO || opt == NBD_OPT_INFO);
+ trace_nbd_opt_info_go_start(nbd_opt_lookup(opt), info->name);
buf = g_malloc(4 + len + 2 + 2 * info->request_sizes + 1);
stl_be_p(buf, len);
- memcpy(buf + 4, wantname, len);
+ memcpy(buf + 4, info->name, len);
/* At most one request, everything else up to server */
stw_be_p(buf + 4 + len, info->request_sizes);
if (info->request_sizes) {
stw_be_p(buf + 4 + len + 2, NBD_INFO_BLOCK_SIZE);
}
- error = nbd_send_option_request(ioc, NBD_OPT_GO,
+ error = nbd_send_option_request(ioc, opt,
4 + len + 2 + 2 * info->request_sizes,
buf, errp);
g_free(buf);
@@ -358,7 +372,7 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
}
while (1) {
- if (nbd_receive_option_reply(ioc, NBD_OPT_GO, &reply, errp) < 0) {
+ if (nbd_receive_option_reply(ioc, opt, &reply, errp) < 0) {
return -1;
}
error = nbd_handle_reply_err(ioc, &reply, errp);
@@ -368,8 +382,10 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
len = reply.length;
if (reply.type == NBD_REP_ACK) {
- /* Server is done sending info and moved into transmission
- phase, but make sure it sent flags */
+ /*
+ * Server is done sending info, and moved into transmission
+ * phase for NBD_OPT_GO, but make sure it sent flags
+ */
if (len) {
error_setg(errp, "server sent invalid NBD_REP_ACK");
return -1;
@@ -378,7 +394,7 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
error_setg(errp, "broken server omitted NBD_INFO_EXPORT");
return -1;
}
- trace_nbd_opt_go_success();
+ trace_nbd_opt_info_go_success(nbd_opt_lookup(opt));
return 1;
}
if (reply.type != NBD_REP_INFO) {
@@ -472,12 +488,12 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
nbd_send_opt_abort(ioc);
return -1;
}
- trace_nbd_opt_go_info_block_size(info->min_block, info->opt_block,
- info->max_block);
+ trace_nbd_opt_info_block_size(info->min_block, info->opt_block,
+ info->max_block);
break;
default:
- trace_nbd_opt_go_info_unknown(type, nbd_info_lookup(type));
+ trace_nbd_opt_info_unknown(type, nbd_info_lookup(type));
if (nbd_drop(ioc, len, errp) < 0) {
error_prepend(errp, "Failed to read info payload: ");
nbd_send_opt_abort(ioc);
@@ -493,7 +509,8 @@ static int nbd_receive_query_exports(QIOChannel *ioc,
const char *wantname,
Error **errp)
{
- bool foundExport = false;
+ bool list_empty = true;
+ bool found_export = false;
trace_nbd_receive_query_exports_start(wantname);
if (nbd_send_option_request(ioc, NBD_OPT_LIST, 0, NULL, errp) < 0) {
@@ -501,14 +518,25 @@ static int nbd_receive_query_exports(QIOChannel *ioc,
}
while (1) {
- int ret = nbd_receive_list(ioc, wantname, &foundExport, errp);
+ char *name;
+ int ret = nbd_receive_list(ioc, &name, NULL, errp);
if (ret < 0) {
/* Server gave unexpected reply */
return -1;
} else if (ret == 0) {
/* Done iterating. */
- if (!foundExport) {
+ if (list_empty) {
+ /*
+ * We don't have enough context to tell a server that
+ * sent an empty list apart from a server that does
+ * not support the list command; but as this function
+ * is just used to trigger a nicer error message
+ * before trying NBD_OPT_EXPORT_NAME, assume the
+ * export is available.
+ */
+ return 0;
+ } else if (!found_export) {
error_setg(errp, "No export with name '%s' available",
wantname);
nbd_send_opt_abort(ioc);
@@ -517,6 +545,11 @@ static int nbd_receive_query_exports(QIOChannel *ioc,
trace_nbd_receive_query_exports_success(wantname);
return 0;
}
+ list_empty = false;
+ if (!strcmp(name, wantname)) {
+ found_export = true;
+ }
+ g_free(name);
}
}
@@ -605,51 +638,67 @@ static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
return QIO_CHANNEL(tioc);
}
-/* nbd_negotiate_simple_meta_context:
- * Set one meta context. Simple means that reply must contain zero (not
- * negotiated) or one (negotiated) contexts. More contexts would be considered
- * as a protocol error. It's also implied that meta-data query equals queried
- * context name, so, if server replies with something different than @context,
- * it is considered an error too.
- * return 1 for successful negotiation, context_id is set
- * 0 if operation is unsupported,
- * -1 with errp set for any other error
+/*
+ * nbd_send_meta_query:
+ * Send 0 or 1 set/list meta context queries.
+ * Return 0 on success, -1 with errp set for any error
*/
-static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
- const char *export,
- const char *context,
- uint32_t *context_id,
- Error **errp)
+static int nbd_send_meta_query(QIOChannel *ioc, uint32_t opt,
+ const char *export, const char *query,
+ Error **errp)
{
int ret;
- NBDOptionReply reply;
- uint32_t received_id = 0;
- bool received = false;
uint32_t export_len = strlen(export);
- uint32_t context_len = strlen(context);
- uint32_t data_len = sizeof(export_len) + export_len +
- sizeof(uint32_t) + /* number of queries */
- sizeof(context_len) + context_len;
- char *data = g_malloc(data_len);
- char *p = data;
-
- trace_nbd_opt_meta_request(context, export);
+ uint32_t queries = !!query;
+ uint32_t query_len = 0;
+ uint32_t data_len;
+ char *data;
+ char *p;
+
+ data_len = sizeof(export_len) + export_len + sizeof(queries);
+ if (query) {
+ query_len = strlen(query);
+ data_len += sizeof(query_len) + query_len;
+ } else {
+ assert(opt == NBD_OPT_LIST_META_CONTEXT);
+ }
+ p = data = g_malloc(data_len);
+
+ trace_nbd_opt_meta_request(nbd_opt_lookup(opt), query ?: "(all)", export);
stl_be_p(p, export_len);
memcpy(p += sizeof(export_len), export, export_len);
- stl_be_p(p += export_len, 1);
- stl_be_p(p += sizeof(uint32_t), context_len);
- memcpy(p += sizeof(context_len), context, context_len);
+ stl_be_p(p += export_len, queries);
+ if (query) {
+ stl_be_p(p += sizeof(queries), query_len);
+ memcpy(p += sizeof(query_len), query, query_len);
+ }
- ret = nbd_send_option_request(ioc, NBD_OPT_SET_META_CONTEXT, data_len, data,
- errp);
+ ret = nbd_send_option_request(ioc, opt, data_len, data, errp);
g_free(data);
- if (ret < 0) {
- return ret;
- }
+ return ret;
+}
- if (nbd_receive_option_reply(ioc, NBD_OPT_SET_META_CONTEXT, &reply,
- errp) < 0)
- {
+/*
+ * nbd_receive_one_meta_context:
+ * Called in a loop to receive and trace one set/list meta context reply.
+ * Pass non-NULL @name or @id to collect results back to the caller, which
+ * must eventually call g_free().
+ * return 1 if name is set and iteration must continue,
+ * 0 if iteration is complete (including if option is unsupported),
+ * -1 with errp set for any error
+ */
+static int nbd_receive_one_meta_context(QIOChannel *ioc,
+ uint32_t opt,
+ char **name,
+ uint32_t *id,
+ Error **errp)
+{
+ int ret;
+ NBDOptionReply reply;
+ char *local_name = NULL;
+ uint32_t local_id;
+
+ if (nbd_receive_option_reply(ioc, opt, &reply, errp) < 0) {
return -1;
}
@@ -658,29 +707,92 @@ static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
return ret;
}
- if (reply.type == NBD_REP_META_CONTEXT) {
- char *name;
-
- if (reply.length != sizeof(received_id) + context_len) {
- error_setg(errp, "Failed to negotiate meta context '%s', server "
- "answered with unexpected length %" PRIu32, context,
- reply.length);
+ if (reply.type == NBD_REP_ACK) {
+ if (reply.length != 0) {
+ error_setg(errp, "Unexpected length to ACK response");
nbd_send_opt_abort(ioc);
return -1;
}
+ return 0;
+ } else if (reply.type != NBD_REP_META_CONTEXT) {
+ error_setg(errp, "Unexpected reply type %u (%s), expected %u (%s)",
+ reply.type, nbd_rep_lookup(reply.type),
+ NBD_REP_META_CONTEXT, nbd_rep_lookup(NBD_REP_META_CONTEXT));
+ nbd_send_opt_abort(ioc);
+ return -1;
+ }
- if (nbd_read(ioc, &received_id, sizeof(received_id), errp) < 0) {
- return -1;
- }
- received_id = be32_to_cpu(received_id);
+ if (reply.length <= sizeof(local_id) ||
+ reply.length > NBD_MAX_BUFFER_SIZE) {
+ error_setg(errp, "Failed to negotiate meta context, server "
+ "answered with unexpected length %" PRIu32,
+ reply.length);
+ nbd_send_opt_abort(ioc);
+ return -1;
+ }
- reply.length -= sizeof(received_id);
- name = g_malloc(reply.length + 1);
- if (nbd_read(ioc, name, reply.length, errp) < 0) {
- g_free(name);
- return -1;
- }
- name[reply.length] = '\0';
+ if (nbd_read(ioc, &local_id, sizeof(local_id), errp) < 0) {
+ return -1;
+ }
+ local_id = be32_to_cpu(local_id);
+
+ reply.length -= sizeof(local_id);
+ local_name = g_malloc(reply.length + 1);
+ if (nbd_read(ioc, local_name, reply.length, errp) < 0) {
+ g_free(local_name);
+ return -1;
+ }
+ local_name[reply.length] = '\0';
+ trace_nbd_opt_meta_reply(nbd_opt_lookup(opt), local_name, local_id);
+
+ if (name) {
+ *name = local_name;
+ } else {
+ g_free(local_name);
+ }
+ if (id) {
+ *id = local_id;
+ }
+ return 1;
+}
+
+/*
+ * nbd_negotiate_simple_meta_context:
+ * Request the server to set the meta context for export @info->name
+ * using @info->x_dirty_bitmap with a fallback to "base:allocation",
+ * setting @info->context_id to the resulting id. Fail if the server
+ * responds with more than one context or with a context different
+ * than the query.
+ * return 1 for successful negotiation,
+ * 0 if operation is unsupported,
+ * -1 with errp set for any other error
+ */
+static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
+ NBDExportInfo *info,
+ Error **errp)
+{
+ /*
+ * TODO: Removing the x_dirty_bitmap hack will mean refactoring
+ * this function to request and store ids for multiple contexts
+ * (both base:allocation and a dirty bitmap), at which point this
+ * function should lose the term _simple.
+ */
+ int ret;
+ const char *context = info->x_dirty_bitmap ?: "base:allocation";
+ bool received = false;
+ char *name = NULL;
+
+ if (nbd_send_meta_query(ioc, NBD_OPT_SET_META_CONTEXT,
+ info->name, context, errp) < 0) {
+ return -1;
+ }
+
+ ret = nbd_receive_one_meta_context(ioc, NBD_OPT_SET_META_CONTEXT,
+ &name, &info->context_id, errp);
+ if (ret < 0) {
+ return -1;
+ }
+ if (ret == 1) {
if (strcmp(context, name)) {
error_setg(errp, "Failed to negotiate meta context '%s', server "
"answered with different context '%s'", context,
@@ -690,84 +802,115 @@ static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
return -1;
}
g_free(name);
-
- trace_nbd_opt_meta_reply(context, received_id);
received = true;
- /* receive NBD_REP_ACK */
- if (nbd_receive_option_reply(ioc, NBD_OPT_SET_META_CONTEXT, &reply,
- errp) < 0)
- {
+ ret = nbd_receive_one_meta_context(ioc, NBD_OPT_SET_META_CONTEXT,
+ NULL, NULL, errp);
+ if (ret < 0) {
return -1;
}
-
- ret = nbd_handle_reply_err(ioc, &reply, errp);
- if (ret <= 0) {
- return ret;
- }
}
-
- if (reply.type != NBD_REP_ACK) {
- error_setg(errp, "Unexpected reply type %u (%s), expected %u (%s)",
- reply.type, nbd_rep_lookup(reply.type),
- NBD_REP_ACK, nbd_rep_lookup(NBD_REP_ACK));
+ if (ret != 0) {
+ error_setg(errp, "Server answered with more than one context");
nbd_send_opt_abort(ioc);
return -1;
}
- if (reply.length) {
- error_setg(errp, "Unexpected length to ACK response");
- nbd_send_opt_abort(ioc);
+ return received;
+}
+
+/*
+ * nbd_list_meta_contexts:
+ * Request the server to list all meta contexts for export @info->name.
+ * return 0 if list is complete (even if empty),
+ * -1 with errp set for any error
+ */
+static int nbd_list_meta_contexts(QIOChannel *ioc,
+ NBDExportInfo *info,
+ Error **errp)
+{
+ int ret;
+ int seen_any = false;
+ int seen_qemu = false;
+
+ if (nbd_send_meta_query(ioc, NBD_OPT_LIST_META_CONTEXT,
+ info->name, NULL, errp) < 0) {
return -1;
}
- if (received) {
- *context_id = received_id;
- return 1;
+ while (1) {
+ char *context;
+
+ ret = nbd_receive_one_meta_context(ioc, NBD_OPT_LIST_META_CONTEXT,
+ &context, NULL, errp);
+ if (ret == 0 && seen_any && !seen_qemu) {
+ /*
+ * Work around qemu 3.0 bug: the server forgot to send
+ * "qemu:" replies to 0 queries. If we saw at least one
+ * reply (probably base:allocation), but none of them were
+ * qemu:, then run a more specific query to make sure.
+ */
+ seen_qemu = true;
+ if (nbd_send_meta_query(ioc, NBD_OPT_LIST_META_CONTEXT,
+ info->name, "qemu:", errp) < 0) {
+ return -1;
+ }
+ continue;
+ }
+ if (ret <= 0) {
+ return ret;
+ }
+ seen_any = true;
+ seen_qemu |= strstart(context, "qemu:", NULL);
+ info->contexts = g_renew(char *, info->contexts, ++info->n_contexts);
+ info->contexts[info->n_contexts - 1] = context;
}
-
- return 0;
}
-int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
- QCryptoTLSCreds *tlscreds, const char *hostname,
- QIOChannel **outioc, NBDExportInfo *info,
- Error **errp)
+/*
+ * nbd_start_negotiate:
+ * Start the handshake to the server. After a positive return, the server
+ * is ready to accept additional NBD_OPT requests.
+ * Returns: negative errno: failure talking to server
+ * 0: server is oldstyle, must call nbd_negotiate_finish_oldstyle
+ * 1: server is newstyle, but can only accept EXPORT_NAME
+ * 2: server is newstyle, but lacks structured replies
+ * 3: server is newstyle and set up for structured replies
+ */
+static int nbd_start_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
+ const char *hostname, QIOChannel **outioc,
+ bool structured_reply, bool *zeroes,
+ Error **errp)
{
uint64_t magic;
- int rc;
- bool zeroes = true;
- bool structured_reply = info->structured_reply;
- bool base_allocation = info->base_allocation;
- trace_nbd_receive_negotiate(tlscreds, hostname ? hostname : "<null>");
-
- info->structured_reply = false;
- info->base_allocation = false;
- rc = -EINVAL;
+ trace_nbd_start_negotiate(tlscreds, hostname ? hostname : "<null>");
+ if (zeroes) {
+ *zeroes = true;
+ }
if (outioc) {
*outioc = NULL;
}
if (tlscreds && !outioc) {
error_setg(errp, "Output I/O channel required for TLS");
- goto fail;
+ return -EINVAL;
}
if (nbd_read(ioc, &magic, sizeof(magic), errp) < 0) {
error_prepend(errp, "Failed to read initial magic: ");
- goto fail;
+ return -EINVAL;
}
magic = be64_to_cpu(magic);
trace_nbd_receive_negotiate_magic(magic);
if (magic != NBD_INIT_MAGIC) {
error_setg(errp, "Bad initial magic received: 0x%" PRIx64, magic);
- goto fail;
+ return -EINVAL;
}
if (nbd_read(ioc, &magic, sizeof(magic), errp) < 0) {
error_prepend(errp, "Failed to read server magic: ");
- goto fail;
+ return -EINVAL;
}
magic = be64_to_cpu(magic);
trace_nbd_receive_negotiate_magic(magic);
@@ -779,7 +922,7 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
if (nbd_read(ioc, &globalflags, sizeof(globalflags), errp) < 0) {
error_prepend(errp, "Failed to read server flags: ");
- goto fail;
+ return -EINVAL;
}
globalflags = be16_to_cpu(globalflags);
trace_nbd_receive_negotiate_server_flags(globalflags);
@@ -788,136 +931,316 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
clientflags |= NBD_FLAG_C_FIXED_NEWSTYLE;
}
if (globalflags & NBD_FLAG_NO_ZEROES) {
- zeroes = false;
+ if (zeroes) {
+ *zeroes = false;
+ }
clientflags |= NBD_FLAG_C_NO_ZEROES;
}
/* client requested flags */
clientflags = cpu_to_be32(clientflags);
if (nbd_write(ioc, &clientflags, sizeof(clientflags), errp) < 0) {
error_prepend(errp, "Failed to send clientflags field: ");
- goto fail;
+ return -EINVAL;
}
if (tlscreds) {
if (fixedNewStyle) {
*outioc = nbd_receive_starttls(ioc, tlscreds, hostname, errp);
if (!*outioc) {
- goto fail;
+ return -EINVAL;
}
ioc = *outioc;
} else {
error_setg(errp, "Server does not support STARTTLS");
- goto fail;
+ return -EINVAL;
}
}
- if (!name) {
- trace_nbd_receive_negotiate_default_name();
- name = "";
- }
if (fixedNewStyle) {
- int result;
+ int result = 0;
if (structured_reply) {
result = nbd_request_simple_option(ioc,
NBD_OPT_STRUCTURED_REPLY,
errp);
if (result < 0) {
- goto fail;
+ return -EINVAL;
}
- info->structured_reply = result == 1;
}
+ return 2 + result;
+ } else {
+ return 1;
+ }
+ } else if (magic == NBD_CLIENT_MAGIC) {
+ if (tlscreds) {
+ error_setg(errp, "Server does not support STARTTLS");
+ return -EINVAL;
+ }
+ return 0;
+ } else {
+ error_setg(errp, "Bad server magic received: 0x%" PRIx64, magic);
+ return -EINVAL;
+ }
+}
- if (info->structured_reply && base_allocation) {
- result = nbd_negotiate_simple_meta_context(
- ioc, name, info->x_dirty_bitmap ?: "base:allocation",
- &info->meta_base_allocation_id, errp);
- if (result < 0) {
- goto fail;
- }
- info->base_allocation = result == 1;
- }
+/*
+ * nbd_negotiate_finish_oldstyle:
+ * Populate @info with the size and export flags from an oldstyle server,
+ * but does not consume 124 bytes of reserved zero padding.
+ * Returns 0 on success, -1 with @errp set on failure
+ */
+static int nbd_negotiate_finish_oldstyle(QIOChannel *ioc, NBDExportInfo *info,
+ Error **errp)
+{
+ uint32_t oldflags;
+
+ if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) {
+ error_prepend(errp, "Failed to read export length: ");
+ return -EINVAL;
+ }
+ info->size = be64_to_cpu(info->size);
+
+ if (nbd_read(ioc, &oldflags, sizeof(oldflags), errp) < 0) {
+ error_prepend(errp, "Failed to read export flags: ");
+ return -EINVAL;
+ }
+ oldflags = be32_to_cpu(oldflags);
+ if (oldflags & ~0xffff) {
+ error_setg(errp, "Unexpected export flags %0x" PRIx32, oldflags);
+ return -EINVAL;
+ }
+ info->flags = oldflags;
+ return 0;
+}
+
+/*
+ * nbd_receive_negotiate:
+ * Connect to server, complete negotiation, and move into transmission phase.
+ * Returns: negative errno: failure talking to server
+ * 0: server is connected
+ */
+int nbd_receive_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
+ const char *hostname, QIOChannel **outioc,
+ NBDExportInfo *info, Error **errp)
+{
+ int result;
+ bool zeroes;
+ bool base_allocation = info->base_allocation;
+
+ assert(info->name);
+ trace_nbd_receive_negotiate_name(info->name);
+
+ result = nbd_start_negotiate(ioc, tlscreds, hostname, outioc,
+ info->structured_reply, &zeroes, errp);
- /* Try NBD_OPT_GO first - if it works, we are done (it
- * also gives us a good message if the server requires
- * TLS). If it is not available, fall back to
- * NBD_OPT_LIST for nicer error messages about a missing
- * export, then use NBD_OPT_EXPORT_NAME. */
- result = nbd_opt_go(ioc, name, info, errp);
+ info->structured_reply = false;
+ info->base_allocation = false;
+ if (tlscreds && *outioc) {
+ ioc = *outioc;
+ }
+
+ switch (result) {
+ case 3: /* newstyle, with structured replies */
+ info->structured_reply = true;
+ if (base_allocation) {
+ result = nbd_negotiate_simple_meta_context(ioc, info, errp);
if (result < 0) {
- goto fail;
- }
- if (result > 0) {
- return 0;
- }
- /* Check our desired export is present in the
- * server export list. Since NBD_OPT_EXPORT_NAME
- * cannot return an error message, running this
- * query gives us better error reporting if the
- * export name is not available.
- */
- if (nbd_receive_query_exports(ioc, name, errp) < 0) {
- goto fail;
+ return -EINVAL;
}
+ info->base_allocation = result == 1;
+ }
+ /* fall through */
+ case 2: /* newstyle, try OPT_GO */
+ /* Try NBD_OPT_GO first - if it works, we are done (it
+ * also gives us a good message if the server requires
+ * TLS). If it is not available, fall back to
+ * NBD_OPT_LIST for nicer error messages about a missing
+ * export, then use NBD_OPT_EXPORT_NAME. */
+ result = nbd_opt_info_or_go(ioc, NBD_OPT_GO, info, errp);
+ if (result < 0) {
+ return -EINVAL;
+ }
+ if (result > 0) {
+ return 0;
}
+ /* Check our desired export is present in the
+ * server export list. Since NBD_OPT_EXPORT_NAME
+ * cannot return an error message, running this
+ * query gives us better error reporting if the
+ * export name is not available.
+ */
+ if (nbd_receive_query_exports(ioc, info->name, errp) < 0) {
+ return -EINVAL;
+ }
+ /* fall through */
+ case 1: /* newstyle, but limited to EXPORT_NAME */
/* write the export name request */
- if (nbd_send_option_request(ioc, NBD_OPT_EXPORT_NAME, -1, name,
+ if (nbd_send_option_request(ioc, NBD_OPT_EXPORT_NAME, -1, info->name,
errp) < 0) {
- goto fail;
+ return -EINVAL;
}
/* Read the response */
if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) {
error_prepend(errp, "Failed to read export length: ");
- goto fail;
+ return -EINVAL;
}
info->size = be64_to_cpu(info->size);
if (nbd_read(ioc, &info->flags, sizeof(info->flags), errp) < 0) {
error_prepend(errp, "Failed to read export flags: ");
- goto fail;
+ return -EINVAL;
}
info->flags = be16_to_cpu(info->flags);
- } else if (magic == NBD_CLIENT_MAGIC) {
- uint32_t oldflags;
+ break;
+ case 0: /* oldstyle, parse length and flags */
+ if (*info->name) {
+ error_setg(errp, "Server does not support non-empty export names");
+ return -EINVAL;
+ }
+ if (nbd_negotiate_finish_oldstyle(ioc, info, errp) < 0) {
+ return -EINVAL;
+ }
+ break;
+ default:
+ return result;
+ }
+
+ trace_nbd_receive_negotiate_size_flags(info->size, info->flags);
+ if (zeroes && nbd_drop(ioc, 124, errp) < 0) {
+ error_prepend(errp, "Failed to read reserved block: ");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Clean up result of nbd_receive_export_list */
+void nbd_free_export_list(NBDExportInfo *info, int count)
+{
+ int i, j;
- if (name) {
- error_setg(errp, "Server does not support export names");
- goto fail;
+ if (!info) {
+ return;
+ }
+
+ for (i = 0; i < count; i++) {
+ g_free(info[i].name);
+ g_free(info[i].description);
+ for (j = 0; j < info[i].n_contexts; j++) {
+ g_free(info[i].contexts[j]);
}
- if (tlscreds) {
- error_setg(errp, "Server does not support STARTTLS");
- goto fail;
+ g_free(info[i].contexts);
+ }
+ g_free(info);
+}
+
+/*
+ * nbd_receive_export_list:
+ * Query details about a server's exports, then disconnect without
+ * going into transmission phase. Return a count of the exports listed
+ * in @info by the server, or -1 on error. Caller must free @info using
+ * nbd_free_export_list().
+ */
+int nbd_receive_export_list(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
+ const char *hostname, NBDExportInfo **info,
+ Error **errp)
+{
+ int result;
+ int count = 0;
+ int i;
+ int rc;
+ int ret = -1;
+ NBDExportInfo *array = NULL;
+ QIOChannel *sioc = NULL;
+
+ *info = NULL;
+ result = nbd_start_negotiate(ioc, tlscreds, hostname, &sioc, true, NULL,
+ errp);
+ if (tlscreds && sioc) {
+ ioc = sioc;
+ }
+
+ switch (result) {
+ case 2:
+ case 3:
+ /* newstyle - use NBD_OPT_LIST to populate array, then try
+ * NBD_OPT_INFO on each array member. If structured replies
+ * are enabled, also try NBD_OPT_LIST_META_CONTEXT. */
+ if (nbd_send_option_request(ioc, NBD_OPT_LIST, 0, NULL, errp) < 0) {
+ goto out;
+ }
+ while (1) {
+ char *name;
+ char *desc;
+
+ rc = nbd_receive_list(ioc, &name, &desc, errp);
+ if (rc < 0) {
+ goto out;
+ } else if (rc == 0) {
+ break;
+ }
+ array = g_renew(NBDExportInfo, array, ++count);
+ memset(&array[count - 1], 0, sizeof(*array));
+ array[count - 1].name = name;
+ array[count - 1].description = desc;
+ array[count - 1].structured_reply = result == 3;
}
- if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) {
- error_prepend(errp, "Failed to read export length: ");
- goto fail;
+ for (i = 0; i < count; i++) {
+ array[i].request_sizes = true;
+ rc = nbd_opt_info_or_go(ioc, NBD_OPT_INFO, &array[i], errp);
+ if (rc < 0) {
+ goto out;
+ } else if (rc == 0) {
+ /*
+ * Pointless to try rest of loop. If OPT_INFO doesn't work,
+ * it's unlikely that meta contexts work either
+ */
+ break;
+ }
+
+ if (result == 3 &&
+ nbd_list_meta_contexts(ioc, &array[i], errp) < 0) {
+ goto out;
+ }
}
- info->size = be64_to_cpu(info->size);
- if (nbd_read(ioc, &oldflags, sizeof(oldflags), errp) < 0) {
- error_prepend(errp, "Failed to read export flags: ");
- goto fail;
+ /* Send NBD_OPT_ABORT as a courtesy before hanging up */
+ nbd_send_opt_abort(ioc);
+ break;
+ case 1: /* newstyle, but limited to EXPORT_NAME */
+ error_setg(errp, "Server does not support export lists");
+ /* We can't even send NBD_OPT_ABORT, so merely hang up */
+ goto out;
+ case 0: /* oldstyle, parse length and flags */
+ array = g_new0(NBDExportInfo, 1);
+ array->name = g_strdup("");
+ count = 1;
+
+ if (nbd_negotiate_finish_oldstyle(ioc, array, errp) < 0) {
+ goto out;
}
- oldflags = be32_to_cpu(oldflags);
- if (oldflags & ~0xffff) {
- error_setg(errp, "Unexpected export flags %0x" PRIx32, oldflags);
- goto fail;
+
+ /* Send NBD_CMD_DISC as a courtesy to the server, but ignore all
+ * errors now that we have the information we wanted. */
+ if (nbd_drop(ioc, 124, NULL) == 0) {
+ NBDRequest request = { .type = NBD_CMD_DISC };
+
+ nbd_send_request(ioc, &request);
}
- info->flags = oldflags;
- } else {
- error_setg(errp, "Bad server magic received: 0x%" PRIx64, magic);
- goto fail;
+ break;
+ default:
+ goto out;
}
- trace_nbd_receive_negotiate_size_flags(info->size, info->flags);
- if (zeroes && nbd_drop(ioc, 124, errp) < 0) {
- error_prepend(errp, "Failed to read reserved block: ");
- goto fail;
- }
- rc = 0;
+ *info = array;
+ array = NULL;
+ ret = count;
-fail:
- return rc;
+ out:
+ qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+ qio_channel_close(ioc, NULL);
+ object_unref(OBJECT(sioc));
+ nbd_free_export_list(array, count);
+ return ret;
}
#ifdef __linux__
diff --git a/nbd/server.c b/nbd/server.c
index 6b13601..cb0d563 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -77,8 +77,8 @@ struct NBDExport {
BlockBackend *blk;
char *name;
char *description;
- off_t dev_offset;
- off_t size;
+ uint64_t dev_offset;
+ uint64_t size;
uint16_t nbdflags;
QTAILQ_HEAD(, NBDClient) clients;
QTAILQ_ENTRY(NBDExport) next;
@@ -1455,8 +1455,8 @@ static void nbd_eject_notifier(Notifier *n, void *data)
nbd_export_close(exp);
}
-NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
- const char *name, const char *description,
+NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
+ uint64_t size, const char *name, const char *desc,
const char *bitmap, uint16_t nbdflags,
void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp)
@@ -1495,17 +1495,13 @@ NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
exp->refcount = 1;
QTAILQ_INIT(&exp->clients);
exp->blk = blk;
+ assert(dev_offset <= INT64_MAX);
exp->dev_offset = dev_offset;
exp->name = g_strdup(name);
- exp->description = g_strdup(description);
+ exp->description = g_strdup(desc);
exp->nbdflags = nbdflags;
- exp->size = size < 0 ? blk_getlength(blk) : size;
- if (exp->size < 0) {
- error_setg_errno(errp, -exp->size,
- "Failed to determine the NBD export's length");
- goto fail;
- }
- exp->size -= exp->size % BDRV_SECTOR_SIZE;
+ assert(size <= INT64_MAX - dev_offset);
+ exp->size = QEMU_ALIGN_DOWN(size, BDRV_SECTOR_SIZE);
if (bitmap) {
BdrvDirtyBitmap *bm = NULL;
@@ -2134,10 +2130,10 @@ static int nbd_co_receive_request(NBDRequestData *req, NBDRequest *request,
return -EROFS;
}
if (request->from > client->exp->size ||
- request->from + request->len > client->exp->size) {
+ request->len > client->exp->size - request->from) {
error_setg(errp, "operation past EOF; From: %" PRIu64 ", Len: %" PRIu32
", Size: %" PRIu64, request->from, request->len,
- (uint64_t)client->exp->size);
+ client->exp->size);
return (request->type == NBD_CMD_WRITE ||
request->type == NBD_CMD_WRITE_ZEROES) ? -ENOSPC : -EINVAL;
}
diff --git a/nbd/trace-events b/nbd/trace-events
index 5492042..7f10ebd 100644
--- a/nbd/trace-events
+++ b/nbd/trace-events
@@ -3,20 +3,21 @@ nbd_send_option_request(uint32_t opt, const char *name, uint32_t len) "Sending o
nbd_receive_option_reply(uint32_t option, const char *optname, uint32_t type, const char *typename, uint32_t length) "Received option reply %" PRIu32" (%s), type %" PRIu32" (%s), len %" PRIu32
nbd_server_error_msg(uint32_t err, const char *type, const char *msg) "server reported error 0x%" PRIx32 " (%s) with additional message: %s"
nbd_reply_err_unsup(uint32_t option, const char *name) "server doesn't understand request %" PRIu32 " (%s), attempting fallback"
-nbd_opt_go_start(const char *name) "Attempting NBD_OPT_GO for export '%s'"
-nbd_opt_go_success(void) "Export is good to go"
-nbd_opt_go_info_unknown(int info, const char *name) "Ignoring unknown info %d (%s)"
-nbd_opt_go_info_block_size(uint32_t minimum, uint32_t preferred, uint32_t maximum) "Block sizes are 0x%" PRIx32 ", 0x%" PRIx32 ", 0x%" PRIx32
+nbd_receive_list(const char *name, const char *desc) "export list includes '%s', description '%s'"
+nbd_opt_info_go_start(const char *opt, const char *name) "Attempting %s for export '%s'"
+nbd_opt_info_go_success(const char *opt) "Export is ready after %s request"
+nbd_opt_info_unknown(int info, const char *name) "Ignoring unknown info %d (%s)"
+nbd_opt_info_block_size(uint32_t minimum, uint32_t preferred, uint32_t maximum) "Block sizes are 0x%" PRIx32 ", 0x%" PRIx32 ", 0x%" PRIx32
nbd_receive_query_exports_start(const char *wantname) "Querying export list for '%s'"
nbd_receive_query_exports_success(const char *wantname) "Found desired export name '%s'"
nbd_receive_starttls_new_client(void) "Setting up TLS"
nbd_receive_starttls_tls_handshake(void) "Starting TLS handshake"
-nbd_opt_meta_request(const char *context, const char *export) "Requesting to set meta context %s for export %s"
-nbd_opt_meta_reply(const char *context, uint32_t id) "Received mapping of context %s to id %" PRIu32
-nbd_receive_negotiate(void *tlscreds, const char *hostname) "Receiving negotiation tlscreds=%p hostname=%s"
+nbd_opt_meta_request(const char *optname, const char *context, const char *export) "Requesting %s %s for export %s"
+nbd_opt_meta_reply(const char *optname, const char *context, uint32_t id) "Received %s mapping of %s to id %" PRIu32
+nbd_start_negotiate(void *tlscreds, const char *hostname) "Receiving negotiation tlscreds=%p hostname=%s"
nbd_receive_negotiate_magic(uint64_t magic) "Magic is 0x%" PRIx64
nbd_receive_negotiate_server_flags(uint32_t globalflags) "Global flags are 0x%" PRIx32
-nbd_receive_negotiate_default_name(void) "Using default NBD export name \"\""
+nbd_receive_negotiate_name(const char *name) "Requesting NBD export name '%s'"
nbd_receive_negotiate_size_flags(uint64_t size, uint16_t flags) "Size is %" PRIu64 ", export flags 0x%" PRIx16
nbd_init_set_socket(void) "Setting NBD socket"
nbd_init_set_block_size(unsigned long block_size) "Setting block size to %lu"
diff --git a/qemu-nbd.c b/qemu-nbd.c
index 51b55f2..1f7b2a0 100644
--- a/qemu-nbd.c
+++ b/qemu-nbd.c
@@ -76,7 +76,8 @@ static void usage(const char *name)
{
(printf) (
"Usage: %s [OPTIONS] FILE\n"
-"QEMU Disk Network Block Device Server\n"
+" or: %s -L [OPTIONS]\n"
+"QEMU Disk Network Block Device Utility\n"
"\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n"
@@ -98,6 +99,7 @@ static void usage(const char *name)
" -B, --bitmap=NAME expose a persistent dirty bitmap\n"
"\n"
"General purpose options:\n"
+" -L, --list list exports available from another NBD server\n"
" --object type,id=ID,... define an object such as 'secret' for providing\n"
" passwords and/or encryption keys\n"
" --tls-creds=ID use id of an earlier --object to provide TLS\n"
@@ -131,7 +133,7 @@ static void usage(const char *name)
" --image-opts treat FILE as a full set of image options\n"
"\n"
QEMU_HELP_BOTTOM "\n"
- , name, NBD_DEFAULT_PORT, "DEVICE");
+ , name, name, NBD_DEFAULT_PORT, "DEVICE");
}
static void version(const char *name)
@@ -176,7 +178,7 @@ static void read_partition(uint8_t *p, struct partition_record *r)
}
static int find_partition(BlockBackend *blk, int partition,
- off_t *offset, off_t *size)
+ uint64_t *offset, uint64_t *size)
{
struct partition_record mbr[4];
uint8_t data[MBR_SIZE];
@@ -243,6 +245,91 @@ static void termsig_handler(int signum)
}
+static int qemu_nbd_client_list(SocketAddress *saddr, QCryptoTLSCreds *tls,
+ const char *hostname)
+{
+ int ret = EXIT_FAILURE;
+ int rc;
+ Error *err = NULL;
+ QIOChannelSocket *sioc;
+ NBDExportInfo *list;
+ int i, j;
+
+ sioc = qio_channel_socket_new();
+ if (qio_channel_socket_connect_sync(sioc, saddr, &err) < 0) {
+ error_report_err(err);
+ return EXIT_FAILURE;
+ }
+ rc = nbd_receive_export_list(QIO_CHANNEL(sioc), tls, hostname, &list,
+ &err);
+ if (rc < 0) {
+ if (err) {
+ error_report_err(err);
+ }
+ goto out;
+ }
+ printf("exports available: %d\n", rc);
+ for (i = 0; i < rc; i++) {
+ printf(" export: '%s'\n", list[i].name);
+ if (list[i].description && *list[i].description) {
+ printf(" description: %s\n", list[i].description);
+ }
+ if (list[i].flags & NBD_FLAG_HAS_FLAGS) {
+ printf(" size: %" PRIu64 "\n", list[i].size);
+ printf(" flags: 0x%x (", list[i].flags);
+ if (list[i].flags & NBD_FLAG_READ_ONLY) {
+ printf(" readonly");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_FLUSH) {
+ printf(" flush");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_FUA) {
+ printf(" fua");
+ }
+ if (list[i].flags & NBD_FLAG_ROTATIONAL) {
+ printf(" rotational");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_TRIM) {
+ printf(" trim");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_WRITE_ZEROES) {
+ printf(" zeroes");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_DF) {
+ printf(" df");
+ }
+ if (list[i].flags & NBD_FLAG_CAN_MULTI_CONN) {
+ printf(" multi");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_RESIZE) {
+ printf(" resize");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_CACHE) {
+ printf(" cache");
+ }
+ printf(" )\n");
+ }
+ if (list[i].min_block) {
+ printf(" min block: %u\n", list[i].min_block);
+ printf(" opt block: %u\n", list[i].opt_block);
+ printf(" max block: %u\n", list[i].max_block);
+ }
+ if (list[i].n_contexts) {
+ printf(" available meta contexts: %d\n", list[i].n_contexts);
+ for (j = 0; j < list[i].n_contexts; j++) {
+ printf(" %s\n", list[i].contexts[j]);
+ }
+ }
+ }
+ nbd_free_export_list(list, rc);
+
+ ret = EXIT_SUCCESS;
+ out:
+ object_unref(OBJECT(sioc));
+ return ret;
+}
+
+
#if HAVE_NBD_DEVICE
static void *show_parts(void *arg)
{
@@ -264,7 +351,7 @@ static void *show_parts(void *arg)
static void *nbd_client_thread(void *arg)
{
char *device = arg;
- NBDExportInfo info = { .request_sizes = false, };
+ NBDExportInfo info = { .request_sizes = false, .name = g_strdup("") };
QIOChannelSocket *sioc;
int fd;
int ret;
@@ -279,7 +366,7 @@ static void *nbd_client_thread(void *arg)
goto out;
}
- ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL,
+ ret = nbd_receive_negotiate(QIO_CHANNEL(sioc),
NULL, NULL, NULL, &info, &local_error);
if (ret < 0) {
if (local_error) {
@@ -318,6 +405,7 @@ static void *nbd_client_thread(void *arg)
}
close(fd);
object_unref(OBJECT(sioc));
+ g_free(info.name);
kill(getpid(), SIGTERM);
return (void *) EXIT_SUCCESS;
@@ -326,6 +414,7 @@ out_fd:
out_socket:
object_unref(OBJECT(sioc));
out:
+ g_free(info.name);
kill(getpid(), SIGTERM);
return (void *) EXIT_FAILURE;
}
@@ -423,7 +512,8 @@ static QemuOptsList qemu_object_opts = {
-static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
+static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, bool list,
+ Error **errp)
{
Object *obj;
QCryptoTLSCreds *creds;
@@ -443,10 +533,18 @@ static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
return NULL;
}
- if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
- error_setg(errp,
- "Expecting TLS credentials with a server endpoint");
- return NULL;
+ if (list) {
+ if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
+ error_setg(errp,
+ "Expecting TLS credentials with a client endpoint");
+ return NULL;
+ }
+ } else {
+ if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+ error_setg(errp,
+ "Expecting TLS credentials with a server endpoint");
+ return NULL;
+ }
}
object_ref(obj);
return creds;
@@ -469,7 +567,8 @@ static void setup_address_and_port(const char **address, const char **port)
static const char *socket_activation_validate_opts(const char *device,
const char *sockpath,
const char *address,
- const char *port)
+ const char *port,
+ bool list)
{
if (device != NULL) {
return "NBD device can't be set when using socket activation";
@@ -487,6 +586,10 @@ static const char *socket_activation_validate_opts(const char *device,
return "TCP port number can't be set when using socket activation";
}
+ if (list) {
+ return "List mode is incompatible with socket activation";
+ }
+
return NULL;
}
@@ -500,17 +603,17 @@ int main(int argc, char **argv)
{
BlockBackend *blk;
BlockDriverState *bs;
- off_t dev_offset = 0;
+ uint64_t dev_offset = 0;
uint16_t nbdflags = 0;
bool disconnect = false;
const char *bindto = NULL;
const char *port = NULL;
char *sockpath = NULL;
char *device = NULL;
- off_t fd_size;
+ int64_t fd_size;
QemuOpts *sn_opts = NULL;
const char *sn_id_or_name = NULL;
- const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:T:D:B:";
+ const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:T:D:B:L";
struct option lopt[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
@@ -523,6 +626,7 @@ int main(int argc, char **argv)
{ "bitmap", required_argument, NULL, 'B' },
{ "connect", required_argument, NULL, 'c' },
{ "disconnect", no_argument, NULL, 'd' },
+ { "list", no_argument, NULL, 'L' },
{ "snapshot", no_argument, NULL, 's' },
{ "load-snapshot", required_argument, NULL, 'l' },
{ "nocache", no_argument, NULL, 'n' },
@@ -546,9 +650,8 @@ int main(int argc, char **argv)
};
int ch;
int opt_ind = 0;
- char *end;
int flags = BDRV_O_RDWR;
- int partition = -1;
+ int partition = 0;
int ret = 0;
bool seen_cache = false;
bool seen_discard = false;
@@ -558,7 +661,7 @@ int main(int argc, char **argv)
Error *local_err = NULL;
BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
QDict *options = NULL;
- const char *export_name = ""; /* Default export name */
+ const char *export_name = NULL; /* defaults to "" later for server mode */
const char *export_description = NULL;
const char *bitmap = NULL;
const char *tlscredsid = NULL;
@@ -566,6 +669,7 @@ int main(int argc, char **argv)
bool writethrough = true;
char *trace_file = NULL;
bool fork_process = false;
+ bool list = false;
int old_stderr = -1;
unsigned socket_activation;
@@ -660,13 +764,8 @@ int main(int argc, char **argv)
port = optarg;
break;
case 'o':
- dev_offset = strtoll (optarg, &end, 0);
- if (*end) {
- error_report("Invalid offset `%s'", optarg);
- exit(EXIT_FAILURE);
- }
- if (dev_offset < 0) {
- error_report("Offset must be positive `%s'", optarg);
+ if (qemu_strtou64(optarg, NULL, 0, &dev_offset) < 0) {
+ error_report("Invalid offset '%s'", optarg);
exit(EXIT_FAILURE);
}
break;
@@ -688,13 +787,9 @@ int main(int argc, char **argv)
flags &= ~BDRV_O_RDWR;
break;
case 'P':
- partition = strtol(optarg, &end, 0);
- if (*end) {
- error_report("Invalid partition `%s'", optarg);
- exit(EXIT_FAILURE);
- }
- if (partition < 1 || partition > 8) {
- error_report("Invalid partition %d", partition);
+ if (qemu_strtoi(optarg, NULL, 0, &partition) < 0 ||
+ partition < 1 || partition > 8) {
+ error_report("Invalid partition '%s'", optarg);
exit(EXIT_FAILURE);
}
break;
@@ -715,15 +810,11 @@ int main(int argc, char **argv)
device = optarg;
break;
case 'e':
- shared = strtol(optarg, &end, 0);
- if (*end) {
+ if (qemu_strtoi(optarg, NULL, 0, &shared) < 0 ||
+ shared < 1) {
error_report("Invalid shared device number '%s'", optarg);
exit(EXIT_FAILURE);
}
- if (shared < 1) {
- error_report("Shared device number must be greater than 0");
- exit(EXIT_FAILURE);
- }
break;
case 'f':
fmt = optarg;
@@ -772,13 +863,33 @@ int main(int argc, char **argv)
case QEMU_NBD_OPT_FORK:
fork_process = true;
break;
+ case 'L':
+ list = true;
+ break;
}
}
- if ((argc - optind) != 1) {
+ if (list) {
+ if (argc != optind) {
+ error_report("List mode is incompatible with a file name");
+ exit(EXIT_FAILURE);
+ }
+ if (export_name || export_description || dev_offset || partition ||
+ device || disconnect || fmt || sn_id_or_name || bitmap ||
+ seen_aio || seen_discard || seen_cache) {
+ error_report("List mode is incompatible with per-device settings");
+ exit(EXIT_FAILURE);
+ }
+ if (fork_process) {
+ error_report("List mode is incompatible with forking");
+ exit(EXIT_FAILURE);
+ }
+ } else if ((argc - optind) != 1) {
error_report("Invalid number of arguments");
error_printf("Try `%s --help' for more information.\n", argv[0]);
exit(EXIT_FAILURE);
+ } else if (!export_name) {
+ export_name = "";
}
qemu_opts_foreach(&qemu_object_opts,
@@ -797,7 +908,8 @@ int main(int argc, char **argv)
} else {
/* Using socket activation - check user didn't use -p etc. */
const char *err_msg = socket_activation_validate_opts(device, sockpath,
- bindto, port);
+ bindto, port,
+ list);
if (err_msg != NULL) {
error_report("%s", err_msg);
exit(EXIT_FAILURE);
@@ -820,7 +932,7 @@ int main(int argc, char **argv)
error_report("TLS is not supported with a host device");
exit(EXIT_FAILURE);
}
- tlscreds = nbd_get_tls_creds(tlscredsid, &local_err);
+ tlscreds = nbd_get_tls_creds(tlscredsid, list, &local_err);
if (local_err) {
error_report("Failed to get TLS creds %s",
error_get_pretty(local_err));
@@ -828,6 +940,11 @@ int main(int argc, char **argv)
}
}
+ if (list) {
+ saddr = nbd_build_socket_address(sockpath, bindto, port);
+ return qemu_nbd_client_list(saddr, tlscreds, bindto);
+ }
+
#if !HAVE_NBD_DEVICE
if (disconnect || device) {
error_report("Kernel /dev/nbdN support not available");
@@ -1005,20 +1122,37 @@ int main(int argc, char **argv)
}
if (dev_offset >= fd_size) {
- error_report("Offset (%lld) has to be smaller than the image size "
- "(%lld)",
- (long long int)dev_offset, (long long int)fd_size);
+ error_report("Offset (%" PRIu64 ") has to be smaller than the image "
+ "size (%" PRId64 ")", dev_offset, fd_size);
exit(EXIT_FAILURE);
}
fd_size -= dev_offset;
- if (partition != -1) {
- ret = find_partition(blk, partition, &dev_offset, &fd_size);
+ if (partition) {
+ uint64_t limit;
+
+ if (dev_offset) {
+ error_report("Cannot request partition and offset together");
+ exit(EXIT_FAILURE);
+ }
+ ret = find_partition(blk, partition, &dev_offset, &limit);
if (ret < 0) {
error_report("Could not find partition %d: %s", partition,
strerror(-ret));
exit(EXIT_FAILURE);
}
+ /*
+ * MBR partition limits are (32-bit << 9); this assert lets
+ * the compiler know that we can't overflow 64 bits.
+ */
+ assert(dev_offset + limit >= dev_offset);
+ if (dev_offset + limit > fd_size) {
+ error_report("Discovered partition %d at offset %" PRIu64
+ " size %" PRIu64 ", but size exceeds file length %"
+ PRId64, partition, dev_offset, limit, fd_size);
+ exit(EXIT_FAILURE);
+ }
+ fd_size = limit;
}
export = nbd_export_new(bs, dev_offset, fd_size, export_name,
diff --git a/qemu-nbd.texi b/qemu-nbd.texi
index 96b1546..386bece 100644
--- a/qemu-nbd.texi
+++ b/qemu-nbd.texi
@@ -2,6 +2,8 @@
@c man begin SYNOPSIS
@command{qemu-nbd} [OPTION]... @var{filename}
+@command{qemu-nbd} @option{-L} [OPTION]...
+
@command{qemu-nbd} @option{-d} @var{dev}
@c man end
@end example
@@ -10,11 +12,19 @@
Export a QEMU disk image using the NBD protocol.
+Other uses:
+@itemize
+@item
+Bind a /dev/nbdX block device to a QEMU server (on Linux).
+@item
+As a client to query exports of a remote NBD server.
+@end itemize
+
@c man end
@c man begin OPTIONS
@var{filename} is a disk image filename, or a set of block
-driver options if @var{--image-opts} is specified.
+driver options if @option{--image-opts} is specified.
@var{dev} is an NBD device.
@@ -25,26 +35,29 @@ See the @code{qemu(1)} manual page for full details of the properties
supported. The common object types that it makes sense to define are the
@code{secret} object, which is used to supply passwords and/or encryption
keys, and the @code{tls-creds} object, which is used to supply TLS
-credentials for the qemu-nbd server.
+credentials for the qemu-nbd server or client.
@item -p, --port=@var{port}
-The TCP port to listen on (default @samp{10809})
+The TCP port to listen on as a server, or connect to as a client
+(default @samp{10809}).
@item -o, --offset=@var{offset}
-The offset into the image
+The offset into the image.
@item -b, --bind=@var{iface}
-The interface to bind to (default @samp{0.0.0.0})
+The interface to bind to as a server, or connect to as a client
+(default @samp{0.0.0.0}).
@item -k, --socket=@var{path}
-Use a unix socket with path @var{path}
+Use a unix socket with path @var{path}.
@item --image-opts
Treat @var{filename} as a set of image options, instead of a plain
filename. If this flag is specified, the @var{-f} flag should
not be used, instead the '@code{format=}' option should be set.
@item -f, --format=@var{fmt}
Force the use of the block driver for format @var{fmt} instead of
-auto-detecting
+auto-detecting.
@item -r, --read-only
-Export the disk as read-only
+Export the disk as read-only.
@item -P, --partition=@var{num}
-Only expose partition @var{num}
+Only expose MBR partition @var{num}. Understands physical partitions
+1-4 and logical partitions 5-8.
@item -B, --bitmap=@var{name}
If @var{filename} has a qcow2 persistent bitmap @var{name}, expose
that bitmap via the ``qemu:dirty-bitmap:@var{name}'' context
@@ -52,7 +65,7 @@ accessible through NBD_OPT_SET_META_CONTEXT.
@item -s, --snapshot
Use @var{filename} as an external snapshot, create a temporary
file with backing_file=@var{filename}, redirect the write to
-the temporary one
+the temporary one.
@item -l, --load-snapshot=@var{snapshot_param}
Load an internal snapshot inside @var{filename} and export it
as an read-only device, @var{snapshot_param} format is
@@ -76,31 +89,38 @@ driver-specific optimized zero write commands. @var{detect-zeroes} is one of
converts a zero write to an unmap operation and can only be used if
@var{discard} is set to @samp{unmap}. The default is @samp{off}.
@item -c, --connect=@var{dev}
-Connect @var{filename} to NBD device @var{dev}
+Connect @var{filename} to NBD device @var{dev} (Linux only).
@item -d, --disconnect
-Disconnect the device @var{dev}
+Disconnect the device @var{dev} (Linux only).
@item -e, --shared=@var{num}
-Allow up to @var{num} clients to share the device (default @samp{1})
+Allow up to @var{num} clients to share the device (default
+@samp{1}). Safe for readers, but for now, consistency is not
+guaranteed between multiple writers.
@item -t, --persistent
-Don't exit on the last connection
+Don't exit on the last connection.
@item -x, --export-name=@var{name}
-Set the NBD volume export name. This switches the server to use
-the new style NBD protocol negotiation
+Set the NBD volume export name (default of a zero-length string).
@item -D, --description=@var{description}
Set the NBD volume export description, as a human-readable
-string. Requires the use of @option{-x}
+string.
+@item -L, --list
+Connect as a client and list all details about the exports exposed by
+a remote NBD server. This enables list mode, and is incompatible
+with options that change behavior related to a specific export (such as
+@option{--export-name}, @option{--offset}, ...).
@item --tls-creds=ID
Enable mandatory TLS encryption for the server by setting the ID
of the TLS credentials object previously created with the --object
-option.
+option; or provide the credentials needed for connecting as a client
+in list mode.
@item --fork
Fork off the server process and exit the parent once the server is running.
@item -v, --verbose
-Display extra debugging information
+Display extra debugging information.
@item -h, --help
-Display this help and exit
+Display this help and exit.
@item -V, --version
-Display version information and exit
+Display version information and exit.
@item -T, --trace [[enable=]@var{pattern}][,events=@var{file}][,file=@var{file}]
@findex --trace
@include qemu-option-trace.texi
@@ -108,6 +128,63 @@ Display version information and exit
@c man end
+@c man begin EXAMPLES
+Start a server listening on port 10809 that exposes only the
+guest-visible contents of a qcow2 file, with no TLS encryption, and
+with the default export name (an empty string). The command is
+one-shot, and will block until the first successful client
+disconnects:
+
+@example
+qemu-nbd -f qcow2 file.qcow2
+@end example
+
+Start a long-running server listening with encryption on port 10810,
+and require clients to have a correct X.509 certificate to connect to
+a 1 megabyte subset of a raw file, using the export name 'subset':
+
+@example
+qemu-nbd \
+ --object tls-creds-x509,id=tls0,endpoint=server,dir=/path/to/qemutls \
+ --tls-creds tls0 -t -x subset -p 10810 \
+ --image-opts driver=raw,offset=1M,size=1M,file.driver=file,file.filename=file.raw
+@end example
+
+Serve a read-only copy of just the first MBR partition of a guest
+image over a Unix socket with as many as 5 simultaneous readers, with
+a persistent process forked as a daemon:
+
+@example
+qemu-nbd --fork --persistent --shared=5 --socket=/path/to/sock \
+ --partition=1 --read-only --format=qcow2 file.qcow2
+@end example
+
+Expose the guest-visible contents of a qcow2 file via a block device
+/dev/nbd0 (and possibly creating /dev/nbd0p1 and friends for
+partitions found within), then disconnect the device when done.
+Access to bind qemu-nbd to an /dev/nbd device generally requires root
+privileges, and may also require the execution of @code{modprobe nbd}
+to enable the kernel NBD client module. @emph{CAUTION}: Do not use
+this method to mount filesystems from an untrusted guest image - a
+malicious guest may have prepared the image to attempt to trigger
+kernel bugs in partition probing or file system mounting.
+
+@example
+qemu-nbd -c /dev/nbd0 -f qcow2 file.qcow2
+qemu-nbd -d /dev/nbd0
+@end example
+
+Query a remote server to see details about what export(s) it is
+serving on port 10809, and authenticating via PSK:
+
+@example
+qemu-nbd \
+ --object tls-creds-psk,id=tls0,dir=/tmp/keys,username=eblake,endpoint=client \
+ --tls-creds tls0 -L -b remote.example.com
+@end example
+
+@c man end
+
@ignore
@setfilename qemu-nbd
diff --git a/scripts/texi2pod.pl b/scripts/texi2pod.pl
index 39ce584..839b791 100755
--- a/scripts/texi2pod.pl
+++ b/scripts/texi2pod.pl
@@ -398,7 +398,7 @@ $sects{NAME} = "$fn \- $tl\n";
$sects{FOOTNOTES} .= "=back\n" if exists $sects{FOOTNOTES};
for $sect (qw(NAME SYNOPSIS DESCRIPTION OPTIONS ENVIRONMENT FILES
- BUGS NOTES FOOTNOTES SEEALSO AUTHOR COPYRIGHT)) {
+ BUGS NOTES FOOTNOTES EXAMPLES SEEALSO AUTHOR COPYRIGHT)) {
if(exists $sects{$sect}) {
$head = $sect;
$head =~ s/SEEALSO/SEE ALSO/;
diff --git a/tests/qemu-iotests/223 b/tests/qemu-iotests/223
index 773892d..f120a01 100755
--- a/tests/qemu-iotests/223
+++ b/tests/qemu-iotests/223
@@ -127,6 +127,7 @@ _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix",
"data":{"path":"'"$TEST_DIR/nbd"1'"}}}}' "error" # Attempt second server
+$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "bitmap":"b"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
@@ -142,6 +143,7 @@ _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "name":"n2", "writable":true,
"bitmap":"b2"}}' "return"
+$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd"
echo
echo "=== Contrast normal status to large granularity dirty-bitmap ==="
diff --git a/tests/qemu-iotests/223.out b/tests/qemu-iotests/223.out
index 0de5240..6476b77 100644
--- a/tests/qemu-iotests/223.out
+++ b/tests/qemu-iotests/223.out
@@ -30,12 +30,32 @@ wrote 2097152/2097152 bytes at offset 2097152
{"error": {"class": "GenericError", "desc": "NBD server not running"}}
{"return": {}}
{"error": {"class": "GenericError", "desc": "NBD server already running"}}
+exports available: 0
{"return": {}}
{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}}
{"error": {"class": "GenericError", "desc": "NBD server already has export named 'n'"}}
{"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}}
{"error": {"class": "GenericError", "desc": "Bitmap 'b3' is not found"}}
{"return": {}}
+exports available: 2
+ export: 'n'
+ size: 4194304
+ flags: 0x4ef ( readonly flush fua trim zeroes df cache )
+ min block: 512
+ opt block: 4096
+ max block: 33554432
+ available meta contexts: 2
+ base:allocation
+ qemu:dirty-bitmap:b
+ export: 'n2'
+ size: 4194304
+ flags: 0x4ed ( flush fua trim zeroes df cache )
+ min block: 512
+ opt block: 4096
+ max block: 33554432
+ available meta contexts: 2
+ base:allocation
+ qemu:dirty-bitmap:b2
=== Contrast normal status to large granularity dirty-bitmap ===
diff --git a/tests/qemu-iotests/233 b/tests/qemu-iotests/233
index 1814efe..fc345a1 100755
--- a/tests/qemu-iotests/233
+++ b/tests/qemu-iotests/233
@@ -2,7 +2,7 @@
#
# Test NBD TLS certificate / authorization integration
#
-# Copyright (C) 2018 Red Hat, Inc.
+# Copyright (C) 2018-2019 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -30,6 +30,7 @@ _cleanup()
{
nbd_server_stop
_cleanup_test_img
+ rm -f "$TEST_DIR/server.log"
tls_x509_cleanup
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -66,12 +67,14 @@ $QEMU_IO -c 'w -P 0x11 1m 1m' "$TEST_IMG" | _filter_qemu_io
echo
echo "== check TLS client to plain server fails =="
-nbd_server_start_tcp_socket -f $IMGFMT "$TEST_IMG"
+nbd_server_start_tcp_socket -f $IMGFMT "$TEST_IMG" 2> "$TEST_DIR/server.log"
-$QEMU_IMG info --image-opts \
- --object tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0 \
+obj=tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0
+$QEMU_IMG info --image-opts --object $obj \
driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \
2>&1 | sed "s/$nbd_tcp_port/PORT/g"
+$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port --object $obj \
+ --tls-creds=tls0
nbd_server_stop
@@ -81,23 +84,28 @@ echo "== check plain client to TLS server fails =="
nbd_server_start_tcp_socket \
--object tls-creds-x509,dir=${tls_dir}/server1,endpoint=server,id=tls0,verify-peer=yes \
--tls-creds tls0 \
- -f $IMGFMT "$TEST_IMG"
+ -f $IMGFMT "$TEST_IMG" 2>> "$TEST_DIR/server.log"
$QEMU_IMG info nbd://localhost:$nbd_tcp_port 2>&1 | sed "s/$nbd_tcp_port/PORT/g"
+$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port
echo
echo "== check TLS works =="
-$QEMU_IMG info --image-opts \
- --object tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0 \
+obj=tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0
+$QEMU_IMG info --image-opts --object $obj \
driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \
2>&1 | sed "s/$nbd_tcp_port/PORT/g"
+$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port --object $obj \
+ --tls-creds=tls0
echo
echo "== check TLS with different CA fails =="
-$QEMU_IMG info --image-opts \
- --object tls-creds-x509,dir=${tls_dir}/client2,endpoint=client,id=tls0 \
+obj=tls-creds-x509,dir=${tls_dir}/client2,endpoint=client,id=tls0
+$QEMU_IMG info --image-opts --object $obj \
driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \
2>&1 | sed "s/$nbd_tcp_port/PORT/g"
+$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port --object $obj \
+ --tls-creds=tls0
echo
echo "== perform I/O over TLS =="
@@ -109,6 +117,10 @@ $QEMU_IO -c 'r -P 0x11 1m 1m' -c 'w -P 0x22 1m 1m' --image-opts \
$QEMU_IO -f $IMGFMT -r -U -c 'r -P 0x22 1m 1m' "$TEST_IMG" | _filter_qemu_io
+echo
+echo "== final server log =="
+cat "$TEST_DIR/server.log"
+
# success, all done
echo "*** done"
rm -f $seq.full
diff --git a/tests/qemu-iotests/233.out b/tests/qemu-iotests/233.out
index 5f41672..6d45f3b 100644
--- a/tests/qemu-iotests/233.out
+++ b/tests/qemu-iotests/233.out
@@ -15,20 +15,33 @@ wrote 1048576/1048576 bytes at offset 1048576
== check TLS client to plain server fails ==
qemu-img: Could not open 'driver=nbd,host=127.0.0.1,port=PORT,tls-creds=tls0': Denied by server for option 5 (starttls)
server reported: TLS not configured
+qemu-nbd: Denied by server for option 5 (starttls)
+server reported: TLS not configured
== check plain client to TLS server fails ==
qemu-img: Could not open 'nbd://localhost:PORT': TLS negotiation required before option 8 (structured reply)
server reported: Option 0x8 not permitted before TLS
+qemu-nbd: TLS negotiation required before option 8 (structured reply)
+server reported: Option 0x8 not permitted before TLS
== check TLS works ==
image: nbd://127.0.0.1:PORT
file format: nbd
virtual size: 64M (67108864 bytes)
disk size: unavailable
+exports available: 1
+ export: ''
+ size: 67108864
+ flags: 0x4ed ( flush fua trim zeroes df cache )
+ min block: 512
+ opt block: 4096
+ max block: 33554432
+ available meta contexts: 1
+ base:allocation
== check TLS with different CA fails ==
-qemu-nbd: option negotiation failed: Verify failed: No certificate was found.
qemu-img: Could not open 'driver=nbd,host=127.0.0.1,port=PORT,tls-creds=tls0': The certificate hasn't got a known issuer
+qemu-nbd: The certificate hasn't got a known issuer
== perform I/O over TLS ==
read 1048576/1048576 bytes at offset 1048576
@@ -37,4 +50,8 @@ wrote 1048576/1048576 bytes at offset 1048576
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 1048576/1048576 bytes at offset 1048576
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== final server log ==
+qemu-nbd: option negotiation failed: Verify failed: No certificate was found.
+qemu-nbd: option negotiation failed: Verify failed: No certificate was found.
*** done