nbd.c 18.4 KB
Newer Older
1 2 3 4
/*
 * QEMU Block driver for  NBD
 *
 * Copyright (C) 2008 Bull S.A.S.
M
malc 已提交
5
 *     Author: Laurent Vivier <Laurent.Vivier@bull.net>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 * Some parts:
 *    Copyright (C) 2007 Anthony Liguori <anthony@codemonkey.ws>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

P
Peter Maydell 已提交
29
#include "qemu/osdep.h"
M
Marc-André Lureau 已提交
30
#include "block/nbd-client.h"
31
#include "qapi/error.h"
32
#include "qemu/uri.h"
33
#include "block/block_int.h"
34
#include "qemu/module.h"
M
Max Reitz 已提交
35 36 37
#include "qapi-visit.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
38
#include "qapi/qmp/qdict.h"
39 40
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qint.h"
41
#include "qapi/qmp/qstring.h"
42
#include "qemu/cutils.h"
43

44 45
#define EN_OPTSTR ":exportname="

46
typedef struct BDRVNBDState {
47
    NBDClientSession client;
48 49

    /* For nbd_refresh_filename() */
50
    SocketAddressFlat *saddr;
M
Max Reitz 已提交
51
    char *export, *tlscredsid;
52 53
} BDRVNBDState;

54
static int nbd_parse_uri(const char *filename, QDict *options)
P
Paolo Bonzini 已提交
55 56 57 58 59
{
    URI *uri;
    const char *p;
    QueryParams *qp = NULL;
    int ret = 0;
60
    bool is_unix;
P
Paolo Bonzini 已提交
61 62 63 64 65 66 67 68

    uri = uri_parse(filename);
    if (!uri) {
        return -EINVAL;
    }

    /* transport */
    if (!strcmp(uri->scheme, "nbd")) {
69
        is_unix = false;
P
Paolo Bonzini 已提交
70
    } else if (!strcmp(uri->scheme, "nbd+tcp")) {
71
        is_unix = false;
P
Paolo Bonzini 已提交
72
    } else if (!strcmp(uri->scheme, "nbd+unix")) {
73
        is_unix = true;
P
Paolo Bonzini 已提交
74 75 76 77 78 79 80 81
    } else {
        ret = -EINVAL;
        goto out;
    }

    p = uri->path ? uri->path : "/";
    p += strspn(p, "/");
    if (p[0]) {
82
        qdict_put_str(options, "export", p);
P
Paolo Bonzini 已提交
83 84 85
    }

    qp = query_params_parse(uri->query);
86
    if (qp->n > 1 || (is_unix && !qp->n) || (!is_unix && qp->n)) {
P
Paolo Bonzini 已提交
87 88 89 90
        ret = -EINVAL;
        goto out;
    }

91
    if (is_unix) {
P
Paolo Bonzini 已提交
92 93 94 95 96
        /* nbd+unix:///export?socket=path */
        if (uri->server || uri->port || strcmp(qp->p[0].name, "socket")) {
            ret = -EINVAL;
            goto out;
        }
97 98
        qdict_put_str(options, "server.type", "unix");
        qdict_put_str(options, "server.path", qp->p[0].value);
P
Paolo Bonzini 已提交
99
    } else {
100
        QString *host;
M
Max Reitz 已提交
101 102
        char *port_str;

103
        /* nbd[+tcp]://host[:port]/export */
P
Paolo Bonzini 已提交
104 105 106 107
        if (!uri->server) {
            ret = -EINVAL;
            goto out;
        }
108

109 110 111 112 113 114 115 116
        /* strip braces from literal IPv6 address */
        if (uri->server[0] == '[') {
            host = qstring_from_substr(uri->server, 1,
                                       strlen(uri->server) - 2);
        } else {
            host = qstring_from_str(uri->server);
        }

117
        qdict_put_str(options, "server.type", "inet");
118
        qdict_put(options, "server.host", host);
M
Max Reitz 已提交
119 120

        port_str = g_strdup_printf("%d", uri->port ?: NBD_DEFAULT_PORT);
121
        qdict_put_str(options, "server.port", port_str);
M
Max Reitz 已提交
122
        g_free(port_str);
P
Paolo Bonzini 已提交
123 124 125 126 127 128 129 130 131 132
    }

out:
    if (qp) {
        query_params_free(qp);
    }
    uri_free(uri);
    return ret;
}

133 134 135 136 137 138 139 140
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") ||
M
Max Reitz 已提交
141 142
            !strcmp(e->key, "export") ||
            strstart(e->key, "server.", NULL))
143 144 145 146 147 148 149 150 151 152
        {
            error_setg(errp, "Option '%s' cannot be used with a file name",
                       e->key);
            return true;
        }
    }

    return false;
}

153 154
static void nbd_parse_filename(const char *filename, QDict *options,
                               Error **errp)
155
{
156
    char *file;
157 158
    char *export_name;
    const char *host_spec;
159 160
    const char *unixpath;

161
    if (nbd_has_filename_options_conflict(options, errp)) {
162 163 164
        return;
    }

P
Paolo Bonzini 已提交
165
    if (strstr(filename, "://")) {
166 167 168 169 170
        int ret = nbd_parse_uri(filename, options);
        if (ret < 0) {
            error_setg(errp, "No valid URL specified");
        }
        return;
P
Paolo Bonzini 已提交
171 172
    }

173
    file = g_strdup(filename);
174

175 176 177
    export_name = strstr(file, EN_OPTSTR);
    if (export_name) {
        if (export_name[strlen(EN_OPTSTR)] == 0) {
178 179
            goto out;
        }
180 181
        export_name[0] = 0; /* truncate 'file' */
        export_name += strlen(EN_OPTSTR);
182

183
        qdict_put_str(options, "export", export_name);
184 185
    }

186 187
    /* extract the host_spec - fail if it's not nbd:... */
    if (!strstart(file, "nbd:", &host_spec)) {
188
        error_setg(errp, "File name string for NBD must start with 'nbd:'");
189 190
        goto out;
    }
191

192 193 194 195
    if (!*host_spec) {
        goto out;
    }

196 197
    /* are we a UNIX or TCP socket? */
    if (strstart(host_spec, "unix:", &unixpath)) {
198 199
        qdict_put_str(options, "server.type", "unix");
        qdict_put_str(options, "server.path", unixpath);
200
    } else {
201
        InetSocketAddress *addr = g_new(InetSocketAddress, 1);
202

203 204
        if (inet_parse(addr, host_spec, errp)) {
            goto out_inet;
205
        }
206

207 208 209
        qdict_put_str(options, "server.type", "inet");
        qdict_put_str(options, "server.host", addr->host);
        qdict_put_str(options, "server.port", addr->port);
210
    out_inet:
211 212
        qapi_free_InetSocketAddress(addr);
    }
213

214
out:
215
    g_free(file);
216 217
}

M
Max Reitz 已提交
218 219 220
static bool nbd_process_legacy_socket_options(QDict *output_options,
                                              QemuOpts *legacy_opts,
                                              Error **errp)
221
{
M
Max Reitz 已提交
222 223 224 225
    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;
226

M
Max Reitz 已提交
227 228 229
    if (!path && !host && !port) {
        return true;
    }
230

M
Max Reitz 已提交
231 232 233 234 235 236
    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;
237
        }
238
    }
M
Max Reitz 已提交
239 240 241 242 243 244 245 246 247 248

    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;
        }

249 250
        qdict_put_str(output_options, "server.type", "unix");
        qdict_put_str(output_options, "server.path", path);
M
Max Reitz 已提交
251
    } else if (host) {
252 253 254 255
        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));
256
    }
257

M
Max Reitz 已提交
258 259
    return true;
}
260

261 262
static SocketAddressFlat *nbd_config(BDRVNBDState *s, QDict *options,
                                     Error **errp)
M
Max Reitz 已提交
263
{
264
    SocketAddressFlat *saddr = NULL;
M
Max Reitz 已提交
265 266 267 268 269 270 271 272 273
    QDict *addr = NULL;
    QObject *crumpled_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;
274 275
    }

M
Max Reitz 已提交
276 277 278 279
    crumpled_addr = qdict_crumple(addr, errp);
    if (!crumpled_addr) {
        goto done;
    }
280

281 282 283 284 285 286 287 288
    /*
     * FIXME .numeric, .to, .ipv4 or .ipv6 don't work with -drive
     * server.type=inet.  .to doesn't matter, it's ignored anyway.
     * That's because when @options come from -blockdev or
     * blockdev_add, members are typed according to the QAPI schema,
     * but when they come from -drive, they're all QString.  The
     * visitor expects the former.
     */
289
    iv = qobject_input_visitor_new(crumpled_addr);
290
    visit_type_SocketAddressFlat(iv, NULL, &saddr, &local_err);
M
Max Reitz 已提交
291 292 293 294
    if (local_err) {
        error_propagate(errp, local_err);
        goto done;
    }
295

M
Max Reitz 已提交
296 297 298 299
done:
    QDECREF(addr);
    qobject_decref(crumpled_addr);
    visit_free(iv);
300
    return saddr;
301
}
302

303
NBDClientSession *nbd_get_client_session(BlockDriverState *bs)
M
Max Reitz 已提交
304 305 306 307 308
{
    BDRVNBDState *s = bs->opaque;
    return &s->client;
}

309
static QIOChannelSocket *nbd_establish_connection(SocketAddressFlat *saddr_flat,
310
                                                  Error **errp)
311
{
312
    SocketAddressLegacy *saddr = socket_address_crumple(saddr_flat);
313 314
    QIOChannelSocket *sioc;
    Error *local_err = NULL;
315

316
    sioc = qio_channel_socket_new();
317
    qio_channel_set_name(QIO_CHANNEL(sioc), "nbd-client");
318

319 320 321
    qio_channel_socket_connect_sync(sioc,
                                    saddr,
                                    &local_err);
322
    qapi_free_SocketAddressLegacy(saddr);
323
    if (local_err) {
324
        object_unref(OBJECT(sioc));
325 326
        error_propagate(errp, local_err);
        return NULL;
327
    }
328

329
    qio_channel_set_delay(QIO_CHANNEL(sioc), false);
330

331
    return sioc;
332 333
}

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364

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;
}


365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
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",
        },
    },
};

M
Max Reitz 已提交
397 398
static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
                    Error **errp)
399 400
{
    BDRVNBDState *s = bs->opaque;
401 402
    QemuOpts *opts = NULL;
    Error *local_err = NULL;
403 404 405 406
    QIOChannelSocket *sioc = NULL;
    QCryptoTLSCreds *tlscreds = NULL;
    const char *hostname = NULL;
    int ret = -EINVAL;
407

408 409 410 411 412 413 414
    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;
    }

415
    /* Translate @host, @port, and @path to a SocketAddressFlat */
M
Max Reitz 已提交
416 417 418 419
    if (!nbd_process_legacy_socket_options(options, opts, errp)) {
        goto error;
    }

420
    /* Pop the config into our state object. Exit if invalid. */
M
Max Reitz 已提交
421 422
    s->saddr = nbd_config(s, options, errp);
    if (!s->saddr) {
423 424 425
        goto error;
    }

M
Max Reitz 已提交
426 427
    s->export = g_strdup(qemu_opt_get(opts, "export"));

428 429 430
    s->tlscredsid = g_strdup(qemu_opt_get(opts, "tls-creds"));
    if (s->tlscredsid) {
        tlscreds = nbd_get_tls_creds(s->tlscredsid, errp);
431 432 433 434
        if (!tlscreds) {
            goto error;
        }

435
        /* TODO SOCKET_ADDRESS_KIND_FD where fd has AF_INET or AF_INET6 */
436
        if (s->saddr->type != SOCKET_ADDRESS_FLAT_TYPE_INET) {
437 438 439
            error_setg(errp, "TLS only supported over IP sockets");
            goto error;
        }
440
        hostname = s->saddr->u.inet.host;
441 442 443 444 445
    }

    /* establish TCP connection, return error if it fails
     * TODO: Configurable retry-until-timeout behaviour.
     */
M
Max Reitz 已提交
446
    sioc = nbd_establish_connection(s->saddr, errp);
447
    if (!sioc) {
448 449
        ret = -ECONNREFUSED;
        goto error;
450 451
    }

M
Marc-André Lureau 已提交
452
    /* NBD handshake */
453
    ret = nbd_client_init(bs, sioc, s->export,
454 455 456 457 458 459 460 461
                          tlscreds, hostname, errp);
 error:
    if (sioc) {
        object_unref(OBJECT(sioc));
    }
    if (tlscreds) {
        object_unref(OBJECT(tlscreds));
    }
462
    if (ret < 0) {
463
        qapi_free_SocketAddressFlat(s->saddr);
464 465 466
        g_free(s->export);
        g_free(s->tlscredsid);
    }
467
    qemu_opts_del(opts);
468
    return ret;
469 470
}

P
Paolo Bonzini 已提交
471 472
static int nbd_co_flush(BlockDriverState *bs)
{
M
Max Reitz 已提交
473
    return nbd_client_co_flush(bs);
P
Paolo Bonzini 已提交
474 475
}

476 477
static void nbd_refresh_limits(BlockDriverState *bs, Error **errp)
{
478
    bs->bl.max_pdiscard = NBD_MAX_BUFFER_SIZE;
479
    bs->bl.max_pwrite_zeroes = NBD_MAX_BUFFER_SIZE;
480
    bs->bl.max_transfer = NBD_MAX_BUFFER_SIZE;
481 482
}

483 484
static void nbd_close(BlockDriverState *bs)
{
485 486
    BDRVNBDState *s = bs->opaque;

M
Max Reitz 已提交
487
    nbd_client_close(bs);
488

489
    qapi_free_SocketAddressFlat(s->saddr);
490 491
    g_free(s->export);
    g_free(s->tlscredsid);
492 493 494 495 496 497
}

static int64_t nbd_getlength(BlockDriverState *bs)
{
    BDRVNBDState *s = bs->opaque;

M
Marc-André Lureau 已提交
498
    return s->client.size;
499 500
}

501 502
static void nbd_detach_aio_context(BlockDriverState *bs)
{
M
Max Reitz 已提交
503
    nbd_client_detach_aio_context(bs);
504 505 506 507 508
}

static void nbd_attach_aio_context(BlockDriverState *bs,
                                   AioContext *new_context)
{
M
Max Reitz 已提交
509
    nbd_client_attach_aio_context(bs, new_context);
510 511
}

512
static void nbd_refresh_filename(BlockDriverState *bs, QDict *options)
513
{
514
    BDRVNBDState *s = bs->opaque;
515
    QDict *opts = qdict_new();
M
Max Reitz 已提交
516 517 518 519
    QObject *saddr_qdict;
    Visitor *ov;
    const char *host = NULL, *port = NULL, *path = NULL;

520 521
    if (s->saddr->type == SOCKET_ADDRESS_FLAT_TYPE_INET) {
        const InetSocketAddress *inet = &s->saddr->u.inet;
M
Max Reitz 已提交
522 523 524 525
        if (!inet->has_ipv4 && !inet->has_ipv6 && !inet->has_to) {
            host = inet->host;
            port = inet->port;
        }
526 527 528
    } else if (s->saddr->type == SOCKET_ADDRESS_FLAT_TYPE_UNIX) {
        path = s->saddr->u.q_unix.path;
    } /* else can't represent as pseudo-filename */
529

530
    qdict_put_str(opts, "driver", "nbd");
531

M
Max Reitz 已提交
532
    if (path && s->export) {
533
        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
M
Max Reitz 已提交
534 535
                 "nbd+unix:///%s?socket=%s", s->export, path);
    } else if (path && !s->export) {
536
        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
M
Max Reitz 已提交
537 538
                 "nbd+unix://?socket=%s", path);
    } else if (host && s->export) {
M
Max Reitz 已提交
539
        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
M
Max Reitz 已提交
540 541
                 "nbd://%s:%s/%s", host, port, s->export);
    } else if (host && !s->export) {
M
Max Reitz 已提交
542
        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
M
Max Reitz 已提交
543
                 "nbd://%s:%s", host, port);
M
Max Reitz 已提交
544 545
    }

M
Max Reitz 已提交
546
    ov = qobject_output_visitor_new(&saddr_qdict);
547
    visit_type_SocketAddressFlat(ov, NULL, &s->saddr, &error_abort);
M
Max Reitz 已提交
548
    visit_complete(ov, &saddr_qdict);
549
    visit_free(ov);
M
Max Reitz 已提交
550 551
    qdict_put_obj(opts, "server", saddr_qdict);

552
    if (s->export) {
553
        qdict_put_str(opts, "export", s->export);
554
    }
555
    if (s->tlscredsid) {
556
        qdict_put_str(opts, "tls-creds", s->tlscredsid);
557
    }
558

M
Max Reitz 已提交
559
    qdict_flatten(opts);
560 561 562
    bs->full_open_options = opts;
}

563
static BlockDriver bdrv_nbd = {
564 565 566 567 568
    .format_name                = "nbd",
    .protocol_name              = "nbd",
    .instance_size              = sizeof(BDRVNBDState),
    .bdrv_parse_filename        = nbd_parse_filename,
    .bdrv_file_open             = nbd_open,
569 570
    .bdrv_co_preadv             = nbd_client_co_preadv,
    .bdrv_co_pwritev            = nbd_client_co_pwritev,
571
    .bdrv_co_pwrite_zeroes      = nbd_client_co_pwrite_zeroes,
572 573
    .bdrv_close                 = nbd_close,
    .bdrv_co_flush_to_os        = nbd_co_flush,
574
    .bdrv_co_pdiscard           = nbd_client_co_pdiscard,
575
    .bdrv_refresh_limits        = nbd_refresh_limits,
576 577 578
    .bdrv_getlength             = nbd_getlength,
    .bdrv_detach_aio_context    = nbd_detach_aio_context,
    .bdrv_attach_aio_context    = nbd_attach_aio_context,
579
    .bdrv_refresh_filename      = nbd_refresh_filename,
P
Paolo Bonzini 已提交
580 581 582
};

static BlockDriver bdrv_nbd_tcp = {
583 584 585 586 587
    .format_name                = "nbd",
    .protocol_name              = "nbd+tcp",
    .instance_size              = sizeof(BDRVNBDState),
    .bdrv_parse_filename        = nbd_parse_filename,
    .bdrv_file_open             = nbd_open,
588 589
    .bdrv_co_preadv             = nbd_client_co_preadv,
    .bdrv_co_pwritev            = nbd_client_co_pwritev,
590
    .bdrv_co_pwrite_zeroes      = nbd_client_co_pwrite_zeroes,
591 592
    .bdrv_close                 = nbd_close,
    .bdrv_co_flush_to_os        = nbd_co_flush,
593
    .bdrv_co_pdiscard           = nbd_client_co_pdiscard,
594
    .bdrv_refresh_limits        = nbd_refresh_limits,
595 596 597
    .bdrv_getlength             = nbd_getlength,
    .bdrv_detach_aio_context    = nbd_detach_aio_context,
    .bdrv_attach_aio_context    = nbd_attach_aio_context,
598
    .bdrv_refresh_filename      = nbd_refresh_filename,
P
Paolo Bonzini 已提交
599 600 601
};

static BlockDriver bdrv_nbd_unix = {
602 603 604 605 606
    .format_name                = "nbd",
    .protocol_name              = "nbd+unix",
    .instance_size              = sizeof(BDRVNBDState),
    .bdrv_parse_filename        = nbd_parse_filename,
    .bdrv_file_open             = nbd_open,
607 608
    .bdrv_co_preadv             = nbd_client_co_preadv,
    .bdrv_co_pwritev            = nbd_client_co_pwritev,
609
    .bdrv_co_pwrite_zeroes      = nbd_client_co_pwrite_zeroes,
610 611
    .bdrv_close                 = nbd_close,
    .bdrv_co_flush_to_os        = nbd_co_flush,
612
    .bdrv_co_pdiscard           = nbd_client_co_pdiscard,
613
    .bdrv_refresh_limits        = nbd_refresh_limits,
614 615 616
    .bdrv_getlength             = nbd_getlength,
    .bdrv_detach_aio_context    = nbd_detach_aio_context,
    .bdrv_attach_aio_context    = nbd_attach_aio_context,
617
    .bdrv_refresh_filename      = nbd_refresh_filename,
618
};
619 620 621 622

static void bdrv_nbd_init(void)
{
    bdrv_register(&bdrv_nbd);
P
Paolo Bonzini 已提交
623 624
    bdrv_register(&bdrv_nbd_tcp);
    bdrv_register(&bdrv_nbd_unix);
625 626 627
}

block_init(bdrv_nbd_init);