From 86f8cdf3db8bc263ae08885e04ce96c089d1cac8 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Tue, 11 Jun 2019 13:27:19 +0300 Subject: [PATCH] block/nbd: merge nbd-client.* to nbd.c No reason for keeping driver handlers realization separate from driver structure. We can get rid of extra header file. While being here, fix comments style, restore forgotten comments for NBD_FOREACH_REPLY_CHUNK and nbd_reply_chunk_iter_receive, remove extra includes. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20190611102720.86114-3-vsementsov@virtuozzo.com> Reviewed-by: Eric Blake Signed-off-by: Eric Blake --- block/Makefile.objs | 2 +- block/nbd-client.h | 62 ---- block/{nbd-client.c => nbd.c} | 754 ++++++++++++++++++++++++++++++++++++++---- block/trace-events | 2 +- 4 files changed, 687 insertions(+), 133 deletions(-) delete mode 100644 block/nbd-client.h rename block/{nbd-client.c => nbd.c} (64%) diff --git a/block/Makefile.objs b/block/Makefile.objs index ae11605c9f..dbd1522722 100644 --- a/block/Makefile.objs +++ b/block/Makefile.objs @@ -22,7 +22,7 @@ block-obj-y += null.o mirror.o commit.o io.o create.o block-obj-y += throttle-groups.o block-obj-$(CONFIG_LINUX) += nvme.o -block-obj-y += nbd.o nbd-client.o +block-obj-y += nbd.o block-obj-$(CONFIG_SHEEPDOG) += sheepdog.o block-obj-$(CONFIG_LIBISCSI) += iscsi.o block-obj-$(if $(CONFIG_LIBISCSI),y,n) += iscsi-opts.o diff --git a/block/nbd-client.h b/block/nbd-client.h deleted file mode 100644 index 570538f4c8..0000000000 --- a/block/nbd-client.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef NBD_CLIENT_H -#define NBD_CLIENT_H - -#include "block/nbd.h" -#include "block/block_int.h" -#include "io/channel-socket.h" - -#define MAX_NBD_REQUESTS 16 - -typedef struct { - Coroutine *coroutine; - uint64_t offset; /* original offset of the request */ - bool receiving; /* waiting for connection_co? */ -} NBDClientRequest; - -typedef struct NBDClientSession { - QIOChannelSocket *sioc; /* The master data channel */ - QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */ - NBDExportInfo info; - - CoMutex send_mutex; - CoQueue free_sema; - Coroutine *connection_co; - int in_flight; - - NBDClientRequest requests[MAX_NBD_REQUESTS]; - NBDReply reply; - BlockDriverState *bs; - bool quit; -} NBDClientSession; - -NBDClientSession *nbd_get_client_session(BlockDriverState *bs); - -int nbd_client_init(BlockDriverState *bs, - SocketAddress *saddr, - const char *export_name, - QCryptoTLSCreds *tlscreds, - const char *hostname, - const char *x_dirty_bitmap, - Error **errp); -void nbd_client_close(BlockDriverState *bs); - -int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes); -int nbd_client_co_flush(BlockDriverState *bs); -int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, - uint64_t bytes, QEMUIOVector *qiov, int flags); -int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, - int bytes, BdrvRequestFlags flags); -int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, - uint64_t bytes, QEMUIOVector *qiov, int flags); - -void nbd_client_detach_aio_context(BlockDriverState *bs); -void nbd_client_attach_aio_context(BlockDriverState *bs, - AioContext *new_context); - -int coroutine_fn nbd_client_co_block_status(BlockDriverState *bs, - bool want_zero, - int64_t offset, int64_t bytes, - int64_t *pnum, int64_t *map, - BlockDriverState **file); - -#endif /* NBD_CLIENT_H */ diff --git a/block/nbd-client.c b/block/nbd.c similarity index 64% rename from block/nbd-client.c rename to block/nbd.c index f89a67c23b..1f00be2d66 100644 --- a/block/nbd-client.c +++ b/block/nbd.c @@ -30,12 +30,60 @@ #include "qemu/osdep.h" #include "trace.h" -#include "qapi/error.h" -#include "nbd-client.h" +#include "qemu/uri.h" +#include "qemu/option.h" +#include "qemu/cutils.h" + +#include "qapi/qapi-visit-sockets.h" +#include "qapi/qmp/qstring.h" + +#include "block/qdict.h" +#include "block/nbd.h" +#include "block/block_int.h" + +#define EN_OPTSTR ":exportname=" +#define MAX_NBD_REQUESTS 16 #define HANDLE_TO_INDEX(bs, handle) ((handle) ^ (uint64_t)(intptr_t)(bs)) #define INDEX_TO_HANDLE(bs, index) ((index) ^ (uint64_t)(intptr_t)(bs)) +typedef struct { + Coroutine *coroutine; + uint64_t offset; /* original offset of the request */ + bool receiving; /* waiting for connection_co? */ +} NBDClientRequest; + +typedef struct NBDClientSession { + QIOChannelSocket *sioc; /* The master data channel */ + QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */ + NBDExportInfo info; + + CoMutex send_mutex; + CoQueue free_sema; + Coroutine *connection_co; + int in_flight; + + NBDClientRequest requests[MAX_NBD_REQUESTS]; + NBDReply reply; + BlockDriverState *bs; + bool quit; +} NBDClientSession; + +typedef struct BDRVNBDState { + NBDClientSession client; + + /* For nbd_refresh_filename() */ + SocketAddress *saddr; + char *export, *tlscredsid; +} BDRVNBDState; + +static NBDClientSession *nbd_get_client_session(BlockDriverState *bs) +{ + BDRVNBDState *s = bs->opaque; + return &s->client; +} + + static void nbd_recv_coroutines_wake_all(NBDClientSession *s) { int i; @@ -49,6 +97,43 @@ static void nbd_recv_coroutines_wake_all(NBDClientSession *s) } } +static void nbd_client_detach_aio_context(BlockDriverState *bs) +{ + NBDClientSession *client = nbd_get_client_session(bs); + qio_channel_detach_aio_context(QIO_CHANNEL(client->ioc)); +} + +static void nbd_client_attach_aio_context_bh(void *opaque) +{ + BlockDriverState *bs = opaque; + NBDClientSession *client = nbd_get_client_session(bs); + + /* + * The node is still drained, so we know the coroutine has yielded in + * nbd_read_eof(), the only place where bs->in_flight can reach 0, or it is + * entered for the first time. Both places are safe for entering the + * coroutine. + */ + qemu_aio_coroutine_enter(bs->aio_context, client->connection_co); + bdrv_dec_in_flight(bs); +} + +static void nbd_client_attach_aio_context(BlockDriverState *bs, + AioContext *new_context) +{ + NBDClientSession *client = nbd_get_client_session(bs); + qio_channel_attach_aio_context(QIO_CHANNEL(client->ioc), new_context); + + bdrv_inc_in_flight(bs); + + /* + * Need to wait here for the BH to run because the BH must run while the + * node is still drained. + */ + aio_wait_bh_oneshot(new_context, nbd_client_attach_aio_context_bh, bs); +} + + static void nbd_teardown_connection(BlockDriverState *bs) { NBDClientSession *client = nbd_get_client_session(bs); @@ -96,7 +181,8 @@ static coroutine_fn void nbd_connection_entry(void *opaque) break; } - /* There's no need for a mutex on the receive side, because the + /* + * There's no need for a mutex on the receive side, because the * handler acts as a synchronization point and ensures that only * one coroutine is called until the reply finishes. */ @@ -109,7 +195,8 @@ static coroutine_fn void nbd_connection_entry(void *opaque) break; } - /* We're woken up again by the request itself. Note that there + /* + * We're woken up again by the request itself. Note that there * is no race between yielding and reentering connection_co. This * is because: * @@ -244,7 +331,8 @@ static int nbd_parse_offset_hole_payload(NBDClientSession *client, return 0; } -/* nbd_parse_blockstatus_payload +/* + * nbd_parse_blockstatus_payload * Based on our request, we expect only one extent in reply, for the * base:allocation context. */ @@ -323,7 +411,8 @@ static int nbd_parse_blockstatus_payload(NBDClientSession *client, return 0; } -/* nbd_parse_error_payload +/* + * nbd_parse_error_payload * on success @errp contains message describing nbd error reply */ static int nbd_parse_error_payload(NBDStructuredReplyChunk *chunk, @@ -408,8 +497,6 @@ static int nbd_co_receive_offset_data_payload(NBDClientSession *s, } #define NBD_MAX_MALLOC_PAYLOAD 1000 -/* nbd_co_receive_structured_payload - */ static coroutine_fn int nbd_co_receive_structured_payload( NBDClientSession *s, void **payload, Error **errp) { @@ -445,7 +532,8 @@ static coroutine_fn int nbd_co_receive_structured_payload( return 0; } -/* nbd_co_do_receive_one_chunk +/* + * nbd_co_do_receive_one_chunk * for simple reply: * set request_ret to received reply error * if qiov is not NULL: read payload to @qiov @@ -547,7 +635,8 @@ static coroutine_fn int nbd_co_do_receive_one_chunk( return 0; } -/* nbd_co_receive_one_chunk +/* + * nbd_co_receive_one_chunk * Read reply, wake up connection_co and set s->quit if needed. * Return value is a fatal error code or normal nbd reply error code */ @@ -607,14 +696,18 @@ static void nbd_iter_request_error(NBDReplyChunkIter *iter, int ret) } } -/* NBD_FOREACH_REPLY_CHUNK +/* + * NBD_FOREACH_REPLY_CHUNK + * The pointer stored in @payload requires g_free() to free it. */ #define NBD_FOREACH_REPLY_CHUNK(s, iter, handle, structured, \ qiov, reply, payload) \ for (iter = (NBDReplyChunkIter) { .only_structured = structured }; \ nbd_reply_chunk_iter_receive(s, &iter, handle, qiov, reply, payload);) -/* nbd_reply_chunk_iter_receive +/* + * nbd_reply_chunk_iter_receive + * The pointer stored in @payload requires g_free() to free it. */ static bool nbd_reply_chunk_iter_receive(NBDClientSession *s, NBDReplyChunkIter *iter, @@ -716,8 +809,10 @@ static int nbd_co_receive_cmdread_reply(NBDClientSession *s, uint64_t handle, switch (chunk->type) { case NBD_REPLY_TYPE_OFFSET_DATA: - /* special cased in nbd_co_receive_one_chunk, data is already - * in qiov */ + /* + * special cased in nbd_co_receive_one_chunk, data is already + * in qiov + */ break; case NBD_REPLY_TYPE_OFFSET_HOLE: ret = nbd_parse_offset_hole_payload(s, &reply.structured, payload, @@ -838,8 +933,8 @@ static int nbd_co_request(BlockDriverState *bs, NBDRequest *request, return ret ? ret : request_ret; } -int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, - uint64_t bytes, QEMUIOVector *qiov, int flags) +static int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, + uint64_t bytes, QEMUIOVector *qiov, int flags) { int ret, request_ret; Error *local_err = NULL; @@ -892,8 +987,8 @@ int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, return ret ? ret : request_ret; } -int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, - uint64_t bytes, QEMUIOVector *qiov, int flags) +static int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, + uint64_t bytes, QEMUIOVector *qiov, int flags) { NBDClientSession *client = nbd_get_client_session(bs); NBDRequest request = { @@ -916,8 +1011,8 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, return nbd_co_request(bs, &request, qiov); } -int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, - int bytes, BdrvRequestFlags flags) +static int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, + int bytes, BdrvRequestFlags flags) { NBDClientSession *client = nbd_get_client_session(bs); NBDRequest request = { @@ -945,7 +1040,7 @@ int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, return nbd_co_request(bs, &request, NULL); } -int nbd_client_co_flush(BlockDriverState *bs) +static int nbd_client_co_flush(BlockDriverState *bs) { NBDClientSession *client = nbd_get_client_session(bs); NBDRequest request = { .type = NBD_CMD_FLUSH }; @@ -960,7 +1055,8 @@ int nbd_client_co_flush(BlockDriverState *bs) return nbd_co_request(bs, &request, NULL); } -int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes) +static int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, + int bytes) { NBDClientSession *client = nbd_get_client_session(bs); NBDRequest request = { @@ -977,11 +1073,9 @@ int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes) return nbd_co_request(bs, &request, NULL); } -int coroutine_fn nbd_client_co_block_status(BlockDriverState *bs, - bool want_zero, - int64_t offset, int64_t bytes, - int64_t *pnum, int64_t *map, - BlockDriverState **file) +static int coroutine_fn nbd_client_co_block_status( + BlockDriverState *bs, bool want_zero, int64_t offset, int64_t bytes, + int64_t *pnum, int64_t *map, BlockDriverState **file) { int ret, request_ret; NBDExtent extent = { 0 }; @@ -1049,39 +1143,7 @@ int coroutine_fn nbd_client_co_block_status(BlockDriverState *bs, BDRV_BLOCK_OFFSET_VALID; } -void nbd_client_detach_aio_context(BlockDriverState *bs) -{ - NBDClientSession *client = nbd_get_client_session(bs); - qio_channel_detach_aio_context(QIO_CHANNEL(client->ioc)); -} - -static void nbd_client_attach_aio_context_bh(void *opaque) -{ - BlockDriverState *bs = opaque; - NBDClientSession *client = nbd_get_client_session(bs); - - /* The node is still drained, so we know the coroutine has yielded in - * nbd_read_eof(), the only place where bs->in_flight can reach 0, or it is - * entered for the first time. Both places are safe for entering the - * coroutine.*/ - qemu_aio_coroutine_enter(bs->aio_context, client->connection_co); - bdrv_dec_in_flight(bs); -} - -void nbd_client_attach_aio_context(BlockDriverState *bs, - AioContext *new_context) -{ - NBDClientSession *client = nbd_get_client_session(bs); - qio_channel_attach_aio_context(QIO_CHANNEL(client->ioc), new_context); - - bdrv_inc_in_flight(bs); - - /* Need to wait here for the BH to run because the BH must run while the - * node is still drained. */ - aio_wait_bh_oneshot(new_context, nbd_client_attach_aio_context_bh, bs); -} - -void nbd_client_close(BlockDriverState *bs) +static void nbd_client_close(BlockDriverState *bs) { NBDClientSession *client = nbd_get_client_session(bs); NBDRequest request = { .type = NBD_CMD_DISC }; @@ -1179,8 +1241,10 @@ static int nbd_client_connect(BlockDriverState *bs, object_ref(OBJECT(client->ioc)); } - /* Now that we're connected, set the socket to be non-blocking and - * kick the reply mechanism. */ + /* + * Now that we're connected, set the socket to be non-blocking and + * kick the reply mechanism. + */ qio_channel_set_blocking(QIO_CHANNEL(sioc), false, NULL); client->connection_co = qemu_coroutine_create(nbd_connection_entry, client); bdrv_inc_in_flight(bs); @@ -1207,13 +1271,13 @@ static int nbd_client_connect(BlockDriverState *bs, } } -int nbd_client_init(BlockDriverState *bs, - SocketAddress *saddr, - const char *export, - QCryptoTLSCreds *tlscreds, - const char *hostname, - const char *x_dirty_bitmap, - Error **errp) +static int nbd_client_init(BlockDriverState *bs, + SocketAddress *saddr, + const char *export, + QCryptoTLSCreds *tlscreds, + const char *hostname, + const char *x_dirty_bitmap, + Error **errp) { NBDClientSession *client = nbd_get_client_session(bs); @@ -1224,3 +1288,555 @@ int nbd_client_init(BlockDriverState *bs, return nbd_client_connect(bs, saddr, export, tlscreds, hostname, x_dirty_bitmap, errp); } + +static int nbd_parse_uri(const char *filename, QDict *options) +{ + URI *uri; + const char *p; + QueryParams *qp = NULL; + int ret = 0; + bool is_unix; + + uri = uri_parse(filename); + if (!uri) { + return -EINVAL; + } + + /* transport */ + if (!g_strcmp0(uri->scheme, "nbd")) { + is_unix = false; + } else if (!g_strcmp0(uri->scheme, "nbd+tcp")) { + is_unix = false; + } else if (!g_strcmp0(uri->scheme, "nbd+unix")) { + is_unix = true; + } else { + ret = -EINVAL; + goto out; + } + + p = uri->path ? uri->path : "/"; + p += strspn(p, "/"); + if (p[0]) { + qdict_put_str(options, "export", p); + } + + qp = query_params_parse(uri->query); + if (qp->n > 1 || (is_unix && !qp->n) || (!is_unix && qp->n)) { + ret = -EINVAL; + goto out; + } + + if (is_unix) { + /* nbd+unix:///export?socket=path */ + if (uri->server || uri->port || strcmp(qp->p[0].name, "socket")) { + ret = -EINVAL; + goto out; + } + qdict_put_str(options, "server.type", "unix"); + qdict_put_str(options, "server.path", qp->p[0].value); + } else { + QString *host; + char *port_str; + + /* nbd[+tcp]://host[:port]/export */ + if (!uri->server) { + ret = -EINVAL; + goto out; + } + + /* strip braces from literal IPv6 address */ + if (uri->server[0] == '[') { + host = qstring_from_substr(uri->server, 1, + strlen(uri->server) - 1); + } else { + host = qstring_from_str(uri->server); + } + + qdict_put_str(options, "server.type", "inet"); + qdict_put(options, "server.host", host); + + port_str = g_strdup_printf("%d", uri->port ?: NBD_DEFAULT_PORT); + qdict_put_str(options, "server.port", port_str); + g_free(port_str); + } + +out: + if (qp) { + query_params_free(qp); + } + uri_free(uri); + return ret; +} + +static bool nbd_has_filename_options_conflict(QDict *options, Error **errp) +{ + const QDictEntry *e; + + for (e = qdict_first(options); e; e = qdict_next(options, e)) { + if (!strcmp(e->key, "host") || + !strcmp(e->key, "port") || + !strcmp(e->key, "path") || + !strcmp(e->key, "export") || + strstart(e->key, "server.", NULL)) + { + error_setg(errp, "Option '%s' cannot be used with a file name", + e->key); + return true; + } + } + + return false; +} + +static void nbd_parse_filename(const char *filename, QDict *options, + Error **errp) +{ + char *file; + char *export_name; + const char *host_spec; + const char *unixpath; + + if (nbd_has_filename_options_conflict(options, errp)) { + return; + } + + if (strstr(filename, "://")) { + int ret = nbd_parse_uri(filename, options); + if (ret < 0) { + error_setg(errp, "No valid URL specified"); + } + return; + } + + file = g_strdup(filename); + + export_name = strstr(file, EN_OPTSTR); + if (export_name) { + if (export_name[strlen(EN_OPTSTR)] == 0) { + goto out; + } + export_name[0] = 0; /* truncate 'file' */ + export_name += strlen(EN_OPTSTR); + + qdict_put_str(options, "export", export_name); + } + + /* extract the host_spec - fail if it's not nbd:... */ + if (!strstart(file, "nbd:", &host_spec)) { + error_setg(errp, "File name string for NBD must start with 'nbd:'"); + goto out; + } + + if (!*host_spec) { + goto out; + } + + /* are we a UNIX or TCP socket? */ + if (strstart(host_spec, "unix:", &unixpath)) { + qdict_put_str(options, "server.type", "unix"); + qdict_put_str(options, "server.path", unixpath); + } else { + InetSocketAddress *addr = g_new(InetSocketAddress, 1); + + if (inet_parse(addr, host_spec, errp)) { + goto out_inet; + } + + qdict_put_str(options, "server.type", "inet"); + qdict_put_str(options, "server.host", addr->host); + qdict_put_str(options, "server.port", addr->port); + out_inet: + qapi_free_InetSocketAddress(addr); + } + +out: + g_free(file); +} + +static bool nbd_process_legacy_socket_options(QDict *output_options, + QemuOpts *legacy_opts, + Error **errp) +{ + const char *path = qemu_opt_get(legacy_opts, "path"); + const char *host = qemu_opt_get(legacy_opts, "host"); + const char *port = qemu_opt_get(legacy_opts, "port"); + const QDictEntry *e; + + if (!path && !host && !port) { + return true; + } + + for (e = qdict_first(output_options); e; e = qdict_next(output_options, e)) + { + if (strstart(e->key, "server.", NULL)) { + error_setg(errp, "Cannot use 'server' and path/host/port at the " + "same time"); + return false; + } + } + + if (path && host) { + error_setg(errp, "path and host may not be used at the same time"); + return false; + } else if (path) { + if (port) { + error_setg(errp, "port may not be used without host"); + return false; + } + + qdict_put_str(output_options, "server.type", "unix"); + qdict_put_str(output_options, "server.path", path); + } else if (host) { + qdict_put_str(output_options, "server.type", "inet"); + qdict_put_str(output_options, "server.host", host); + qdict_put_str(output_options, "server.port", + port ?: stringify(NBD_DEFAULT_PORT)); + } + + return true; +} + +static SocketAddress *nbd_config(BDRVNBDState *s, QDict *options, + Error **errp) +{ + SocketAddress *saddr = NULL; + QDict *addr = NULL; + Visitor *iv = NULL; + Error *local_err = NULL; + + qdict_extract_subqdict(options, &addr, "server."); + if (!qdict_size(addr)) { + error_setg(errp, "NBD server address missing"); + goto done; + } + + iv = qobject_input_visitor_new_flat_confused(addr, errp); + if (!iv) { + goto done; + } + + visit_type_SocketAddress(iv, NULL, &saddr, &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto done; + } + +done: + qobject_unref(addr); + visit_free(iv); + return saddr; +} + +static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp) +{ + Object *obj; + QCryptoTLSCreds *creds; + + obj = object_resolve_path_component( + object_get_objects_root(), id); + if (!obj) { + error_setg(errp, "No TLS credentials with id '%s'", + id); + return NULL; + } + creds = (QCryptoTLSCreds *) + object_dynamic_cast(obj, TYPE_QCRYPTO_TLS_CREDS); + if (!creds) { + error_setg(errp, "Object with id '%s' is not TLS credentials", + id); + return NULL; + } + + if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) { + error_setg(errp, + "Expecting TLS credentials with a client endpoint"); + return NULL; + } + object_ref(obj); + return creds; +} + + +static QemuOptsList nbd_runtime_opts = { + .name = "nbd", + .head = QTAILQ_HEAD_INITIALIZER(nbd_runtime_opts.head), + .desc = { + { + .name = "host", + .type = QEMU_OPT_STRING, + .help = "TCP host to connect to", + }, + { + .name = "port", + .type = QEMU_OPT_STRING, + .help = "TCP port to connect to", + }, + { + .name = "path", + .type = QEMU_OPT_STRING, + .help = "Unix socket path to connect to", + }, + { + .name = "export", + .type = QEMU_OPT_STRING, + .help = "Name of the NBD export to open", + }, + { + .name = "tls-creds", + .type = QEMU_OPT_STRING, + .help = "ID of the TLS credentials to use", + }, + { + .name = "x-dirty-bitmap", + .type = QEMU_OPT_STRING, + .help = "experimental: expose named dirty bitmap in place of " + "block status", + }, + { /* end of list */ } + }, +}; + +static int nbd_open(BlockDriverState *bs, QDict *options, int flags, + Error **errp) +{ + BDRVNBDState *s = bs->opaque; + QemuOpts *opts = NULL; + Error *local_err = NULL; + QCryptoTLSCreds *tlscreds = NULL; + const char *hostname = NULL; + int ret = -EINVAL; + + opts = qemu_opts_create(&nbd_runtime_opts, NULL, 0, &error_abort); + qemu_opts_absorb_qdict(opts, options, &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto error; + } + + /* Translate @host, @port, and @path to a SocketAddress */ + if (!nbd_process_legacy_socket_options(options, opts, errp)) { + goto error; + } + + /* Pop the config into our state object. Exit if invalid. */ + s->saddr = nbd_config(s, options, errp); + if (!s->saddr) { + goto error; + } + + s->export = g_strdup(qemu_opt_get(opts, "export")); + + s->tlscredsid = g_strdup(qemu_opt_get(opts, "tls-creds")); + if (s->tlscredsid) { + tlscreds = nbd_get_tls_creds(s->tlscredsid, errp); + if (!tlscreds) { + goto error; + } + + /* TODO SOCKET_ADDRESS_KIND_FD where fd has AF_INET or AF_INET6 */ + if (s->saddr->type != SOCKET_ADDRESS_TYPE_INET) { + error_setg(errp, "TLS only supported over IP sockets"); + goto error; + } + hostname = s->saddr->u.inet.host; + } + + /* NBD handshake */ + ret = nbd_client_init(bs, s->saddr, s->export, tlscreds, hostname, + qemu_opt_get(opts, "x-dirty-bitmap"), errp); + + error: + if (tlscreds) { + object_unref(OBJECT(tlscreds)); + } + if (ret < 0) { + qapi_free_SocketAddress(s->saddr); + g_free(s->export); + g_free(s->tlscredsid); + } + qemu_opts_del(opts); + return ret; +} + +static int nbd_co_flush(BlockDriverState *bs) +{ + return nbd_client_co_flush(bs); +} + +static void nbd_refresh_limits(BlockDriverState *bs, Error **errp) +{ + NBDClientSession *s = nbd_get_client_session(bs); + uint32_t min = s->info.min_block; + uint32_t max = MIN_NON_ZERO(NBD_MAX_BUFFER_SIZE, s->info.max_block); + + /* + * If the server did not advertise an alignment: + * - a size that is not sector-aligned implies that an alignment + * of 1 can be used to access those tail bytes + * - advertisement of block status requires an alignment of 1, so + * that we don't violate block layer constraints that block + * status is always aligned (as we can't control whether the + * server will report sub-sector extents, such as a hole at EOF + * on an unaligned POSIX file) + * - otherwise, assume the server is so old that we are safer avoiding + * sub-sector requests + */ + if (!min) { + min = (!QEMU_IS_ALIGNED(s->info.size, BDRV_SECTOR_SIZE) || + s->info.base_allocation) ? 1 : BDRV_SECTOR_SIZE; + } + + bs->bl.request_alignment = min; + bs->bl.max_pdiscard = max; + bs->bl.max_pwrite_zeroes = max; + bs->bl.max_transfer = max; + + if (s->info.opt_block && + s->info.opt_block > bs->bl.opt_transfer) { + bs->bl.opt_transfer = s->info.opt_block; + } +} + +static void nbd_close(BlockDriverState *bs) +{ + BDRVNBDState *s = bs->opaque; + + nbd_client_close(bs); + + qapi_free_SocketAddress(s->saddr); + g_free(s->export); + g_free(s->tlscredsid); +} + +static int64_t nbd_getlength(BlockDriverState *bs) +{ + BDRVNBDState *s = bs->opaque; + + return s->client.info.size; +} + +static void nbd_refresh_filename(BlockDriverState *bs) +{ + BDRVNBDState *s = bs->opaque; + const char *host = NULL, *port = NULL, *path = NULL; + + if (s->saddr->type == SOCKET_ADDRESS_TYPE_INET) { + const InetSocketAddress *inet = &s->saddr->u.inet; + if (!inet->has_ipv4 && !inet->has_ipv6 && !inet->has_to) { + host = inet->host; + port = inet->port; + } + } else if (s->saddr->type == SOCKET_ADDRESS_TYPE_UNIX) { + path = s->saddr->u.q_unix.path; + } /* else can't represent as pseudo-filename */ + + if (path && s->export) { + snprintf(bs->exact_filename, sizeof(bs->exact_filename), + "nbd+unix:///%s?socket=%s", s->export, path); + } else if (path && !s->export) { + snprintf(bs->exact_filename, sizeof(bs->exact_filename), + "nbd+unix://?socket=%s", path); + } else if (host && s->export) { + snprintf(bs->exact_filename, sizeof(bs->exact_filename), + "nbd://%s:%s/%s", host, port, s->export); + } else if (host && !s->export) { + snprintf(bs->exact_filename, sizeof(bs->exact_filename), + "nbd://%s:%s", host, port); + } +} + +static char *nbd_dirname(BlockDriverState *bs, Error **errp) +{ + /* The generic bdrv_dirname() implementation is able to work out some + * directory name for NBD nodes, but that would be wrong. So far there is no + * specification for how "export paths" would work, so NBD does not have + * directory names. */ + error_setg(errp, "Cannot generate a base directory for NBD nodes"); + return NULL; +} + +static const char *const nbd_strong_runtime_opts[] = { + "path", + "host", + "port", + "export", + "tls-creds", + "server.", + + NULL +}; + +static BlockDriver bdrv_nbd = { + .format_name = "nbd", + .protocol_name = "nbd", + .instance_size = sizeof(BDRVNBDState), + .bdrv_parse_filename = nbd_parse_filename, + .bdrv_file_open = nbd_open, + .bdrv_co_preadv = nbd_client_co_preadv, + .bdrv_co_pwritev = nbd_client_co_pwritev, + .bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes, + .bdrv_close = nbd_close, + .bdrv_co_flush_to_os = nbd_co_flush, + .bdrv_co_pdiscard = nbd_client_co_pdiscard, + .bdrv_refresh_limits = nbd_refresh_limits, + .bdrv_getlength = nbd_getlength, + .bdrv_detach_aio_context = nbd_client_detach_aio_context, + .bdrv_attach_aio_context = nbd_client_attach_aio_context, + .bdrv_refresh_filename = nbd_refresh_filename, + .bdrv_co_block_status = nbd_client_co_block_status, + .bdrv_dirname = nbd_dirname, + .strong_runtime_opts = nbd_strong_runtime_opts, +}; + +static BlockDriver bdrv_nbd_tcp = { + .format_name = "nbd", + .protocol_name = "nbd+tcp", + .instance_size = sizeof(BDRVNBDState), + .bdrv_parse_filename = nbd_parse_filename, + .bdrv_file_open = nbd_open, + .bdrv_co_preadv = nbd_client_co_preadv, + .bdrv_co_pwritev = nbd_client_co_pwritev, + .bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes, + .bdrv_close = nbd_close, + .bdrv_co_flush_to_os = nbd_co_flush, + .bdrv_co_pdiscard = nbd_client_co_pdiscard, + .bdrv_refresh_limits = nbd_refresh_limits, + .bdrv_getlength = nbd_getlength, + .bdrv_detach_aio_context = nbd_client_detach_aio_context, + .bdrv_attach_aio_context = nbd_client_attach_aio_context, + .bdrv_refresh_filename = nbd_refresh_filename, + .bdrv_co_block_status = nbd_client_co_block_status, + .bdrv_dirname = nbd_dirname, + .strong_runtime_opts = nbd_strong_runtime_opts, +}; + +static BlockDriver bdrv_nbd_unix = { + .format_name = "nbd", + .protocol_name = "nbd+unix", + .instance_size = sizeof(BDRVNBDState), + .bdrv_parse_filename = nbd_parse_filename, + .bdrv_file_open = nbd_open, + .bdrv_co_preadv = nbd_client_co_preadv, + .bdrv_co_pwritev = nbd_client_co_pwritev, + .bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes, + .bdrv_close = nbd_close, + .bdrv_co_flush_to_os = nbd_co_flush, + .bdrv_co_pdiscard = nbd_client_co_pdiscard, + .bdrv_refresh_limits = nbd_refresh_limits, + .bdrv_getlength = nbd_getlength, + .bdrv_detach_aio_context = nbd_client_detach_aio_context, + .bdrv_attach_aio_context = nbd_client_attach_aio_context, + .bdrv_refresh_filename = nbd_refresh_filename, + .bdrv_co_block_status = nbd_client_co_block_status, + .bdrv_dirname = nbd_dirname, + .strong_runtime_opts = nbd_strong_runtime_opts, +}; + +static void bdrv_nbd_init(void) +{ + bdrv_register(&bdrv_nbd); + bdrv_register(&bdrv_nbd_tcp); + bdrv_register(&bdrv_nbd_unix); +} + +block_init(bdrv_nbd_init); diff --git a/block/trace-events b/block/trace-events index 01fa5eb081..f6e43ee023 100644 --- a/block/trace-events +++ b/block/trace-events @@ -160,7 +160,7 @@ nvme_cmd_map_qiov_iov(void *s, int i, void *page, int pages) "s %p iov[%d] %p pa # iscsi.c iscsi_xcopy(void *src_lun, uint64_t src_off, void *dst_lun, uint64_t dst_off, uint64_t bytes, int ret) "src_lun %p offset %"PRIu64" dst_lun %p offset %"PRIu64" bytes %"PRIu64" ret %d" -# nbd-client.c +# nbd.c nbd_parse_blockstatus_compliance(const char *err) "ignoring extra data from non-compliant server: %s" nbd_structured_read_compliance(const char *type) "server sent non-compliant unaligned read %s chunk" nbd_read_reply_entry_fail(int ret, const char *err) "ret = %d, err: %s" -- 2.11.4.GIT