nbd.c 15.8 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"
35
#include "qapi/qmp/qdict.h"
36 37
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qint.h"
38
#include "qapi/qmp/qstring.h"
39
#include "qemu/cutils.h"
40

41 42
#define EN_OPTSTR ":exportname="

43
typedef struct BDRVNBDState {
M
Marc-André Lureau 已提交
44
    NbdClientSession client;
45 46 47

    /* For nbd_refresh_filename() */
    char *path, *host, *port, *export, *tlscredsid;
48 49
} BDRVNBDState;

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

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

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

    p = uri->path ? uri->path : "/";
    p += strspn(p, "/");
    if (p[0]) {
78
        qdict_put(options, "export", qstring_from_str(p));
P
Paolo Bonzini 已提交
79 80 81
    }

    qp = query_params_parse(uri->query);
82
    if (qp->n > 1 || (is_unix && !qp->n) || (!is_unix && qp->n)) {
P
Paolo Bonzini 已提交
83 84 85 86
        ret = -EINVAL;
        goto out;
    }

87
    if (is_unix) {
P
Paolo Bonzini 已提交
88 89 90 91 92
        /* nbd+unix:///export?socket=path */
        if (uri->server || uri->port || strcmp(qp->p[0].name, "socket")) {
            ret = -EINVAL;
            goto out;
        }
93
        qdict_put(options, "path", qstring_from_str(qp->p[0].value));
P
Paolo Bonzini 已提交
94
    } else {
95
        QString *host;
96
        /* nbd[+tcp]://host[:port]/export */
P
Paolo Bonzini 已提交
97 98 99 100
        if (!uri->server) {
            ret = -EINVAL;
            goto out;
        }
101

102 103 104 105 106 107 108 109 110
        /* 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);
        }

        qdict_put(options, "host", host);
111 112 113 114 115
        if (uri->port) {
            char* port_str = g_strdup_printf("%d", uri->port);
            qdict_put(options, "port", qstring_from_str(port_str));
            g_free(port_str);
        }
P
Paolo Bonzini 已提交
116 117 118 119 120 121 122 123 124 125
    }

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

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
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"))
        {
            error_setg(errp, "Option '%s' cannot be used with a file name",
                       e->key);
            return true;
        }
    }

    return false;
}

145 146
static void nbd_parse_filename(const char *filename, QDict *options,
                               Error **errp)
147
{
148
    char *file;
149 150
    char *export_name;
    const char *host_spec;
151 152
    const char *unixpath;

153
    if (nbd_has_filename_options_conflict(options, errp)) {
154 155 156
        return;
    }

P
Paolo Bonzini 已提交
157
    if (strstr(filename, "://")) {
158 159 160 161 162
        int ret = nbd_parse_uri(filename, options);
        if (ret < 0) {
            error_setg(errp, "No valid URL specified");
        }
        return;
P
Paolo Bonzini 已提交
163 164
    }

165
    file = g_strdup(filename);
166

167 168 169
    export_name = strstr(file, EN_OPTSTR);
    if (export_name) {
        if (export_name[strlen(EN_OPTSTR)] == 0) {
170 171
            goto out;
        }
172 173
        export_name[0] = 0; /* truncate 'file' */
        export_name += strlen(EN_OPTSTR);
174 175

        qdict_put(options, "export", qstring_from_str(export_name));
176 177
    }

178 179
    /* extract the host_spec - fail if it's not nbd:... */
    if (!strstart(file, "nbd:", &host_spec)) {
180
        error_setg(errp, "File name string for NBD must start with 'nbd:'");
181 182
        goto out;
    }
183

184 185 186 187
    if (!*host_spec) {
        goto out;
    }

188 189
    /* are we a UNIX or TCP socket? */
    if (strstart(host_spec, "unix:", &unixpath)) {
190
        qdict_put(options, "path", qstring_from_str(unixpath));
191
    } else {
192 193
        InetSocketAddress *addr = NULL;

194
        addr = inet_parse(host_spec, errp);
195
        if (!addr) {
196 197
            goto out;
        }
198

199 200 201 202
        qdict_put(options, "host", qstring_from_str(addr->host));
        qdict_put(options, "port", qstring_from_str(addr->port));
        qapi_free_InetSocketAddress(addr);
    }
203

204
out:
205
    g_free(file);
206 207
}

208
static SocketAddress *nbd_config(BDRVNBDState *s, QemuOpts *opts, Error **errp)
209
{
210
    SocketAddress *saddr;
211

212 213
    s->path = g_strdup(qemu_opt_get(opts, "path"));
    s->host = g_strdup(qemu_opt_get(opts, "host"));
214
    s->port = g_strdup(qemu_opt_get(opts, "port"));
215 216 217

    if (!s->path == !s->host) {
        if (s->path) {
218
            error_setg(errp, "path and host may not be used at the same time");
219
        } else {
220
            error_setg(errp, "one of path and host must be specified");
221
        }
222
        return NULL;
223
    }
224 225 226 227
    if (s->port && !s->host) {
        error_setg(errp, "port may not be used without host");
        return NULL;
    }
228

229
    saddr = g_new0(SocketAddress, 1);
230

231
    if (s->path) {
232
        UnixSocketAddress *q_unix;
233
        saddr->type = SOCKET_ADDRESS_KIND_UNIX;
234
        q_unix = saddr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
235
        q_unix->path = g_strdup(s->path);
236
    } else {
237
        InetSocketAddress *inet;
238

239
        saddr->type = SOCKET_ADDRESS_KIND_INET;
240
        inet = saddr->u.inet.data = g_new0(InetSocketAddress, 1);
241 242
        inet->host = g_strdup(s->host);
        inet->port = g_strdup(s->port);
243
        if (!inet->port) {
244
            inet->port = g_strdup_printf("%d", NBD_DEFAULT_PORT);
245
        }
246 247
    }

248
    s->client.is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
249

250
    s->export = g_strdup(qemu_opt_get(opts, "export"));
251 252

    return saddr;
253
}
254

M
Max Reitz 已提交
255 256 257 258 259 260
NbdClientSession *nbd_get_client_session(BlockDriverState *bs)
{
    BDRVNBDState *s = bs->opaque;
    return &s->client;
}

261 262
static QIOChannelSocket *nbd_establish_connection(SocketAddress *saddr,
                                                  Error **errp)
263
{
264 265
    QIOChannelSocket *sioc;
    Error *local_err = NULL;
266

267
    sioc = qio_channel_socket_new();
268

269 270 271 272 273 274
    qio_channel_socket_connect_sync(sioc,
                                    saddr,
                                    &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return NULL;
275
    }
276

277
    qio_channel_set_delay(QIO_CHANNEL(sioc), false);
278

279
    return sioc;
280 281
}

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

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


313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
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 已提交
345 346
static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
                    Error **errp)
347 348
{
    BDRVNBDState *s = bs->opaque;
349 350
    QemuOpts *opts = NULL;
    Error *local_err = NULL;
351
    QIOChannelSocket *sioc = NULL;
352
    SocketAddress *saddr = NULL;
353 354 355
    QCryptoTLSCreds *tlscreds = NULL;
    const char *hostname = NULL;
    int ret = -EINVAL;
356

357 358 359 360 361 362 363
    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;
    }

364
    /* Pop the config into our state object. Exit if invalid. */
365
    saddr = nbd_config(s, opts, errp);
366
    if (!saddr) {
367 368 369
        goto error;
    }

370 371 372
    s->tlscredsid = g_strdup(qemu_opt_get(opts, "tls-creds"));
    if (s->tlscredsid) {
        tlscreds = nbd_get_tls_creds(s->tlscredsid, errp);
373 374 375 376 377 378 379 380
        if (!tlscreds) {
            goto error;
        }

        if (saddr->type != SOCKET_ADDRESS_KIND_INET) {
            error_setg(errp, "TLS only supported over IP sockets");
            goto error;
        }
381
        hostname = saddr->u.inet.data->host;
382 383 384 385 386
    }

    /* establish TCP connection, return error if it fails
     * TODO: Configurable retry-until-timeout behaviour.
     */
387 388
    sioc = nbd_establish_connection(saddr, errp);
    if (!sioc) {
389 390
        ret = -ECONNREFUSED;
        goto error;
391 392
    }

M
Marc-André Lureau 已提交
393
    /* NBD handshake */
394
    ret = nbd_client_init(bs, sioc, s->export,
395 396 397 398 399 400 401 402
                          tlscreds, hostname, errp);
 error:
    if (sioc) {
        object_unref(OBJECT(sioc));
    }
    if (tlscreds) {
        object_unref(OBJECT(tlscreds));
    }
403 404 405 406 407 408 409
    if (ret < 0) {
        g_free(s->path);
        g_free(s->host);
        g_free(s->port);
        g_free(s->export);
        g_free(s->tlscredsid);
    }
410
    qapi_free_SocketAddress(saddr);
411
    qemu_opts_del(opts);
412
    return ret;
413 414
}

P
Paolo Bonzini 已提交
415 416
static int nbd_co_flush(BlockDriverState *bs)
{
M
Max Reitz 已提交
417
    return nbd_client_co_flush(bs);
P
Paolo Bonzini 已提交
418 419
}

420 421
static void nbd_refresh_limits(BlockDriverState *bs, Error **errp)
{
422
    bs->bl.max_pdiscard = NBD_MAX_BUFFER_SIZE;
423
    bs->bl.max_transfer = NBD_MAX_BUFFER_SIZE;
424 425
}

426 427
static void nbd_close(BlockDriverState *bs)
{
428 429
    BDRVNBDState *s = bs->opaque;

M
Max Reitz 已提交
430
    nbd_client_close(bs);
431 432 433 434 435 436

    g_free(s->path);
    g_free(s->host);
    g_free(s->port);
    g_free(s->export);
    g_free(s->tlscredsid);
437 438 439 440 441 442
}

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

M
Marc-André Lureau 已提交
443
    return s->client.size;
444 445
}

446 447
static void nbd_detach_aio_context(BlockDriverState *bs)
{
M
Max Reitz 已提交
448
    nbd_client_detach_aio_context(bs);
449 450 451 452 453
}

static void nbd_attach_aio_context(BlockDriverState *bs,
                                   AioContext *new_context)
{
M
Max Reitz 已提交
454
    nbd_client_attach_aio_context(bs, new_context);
455 456
}

457
static void nbd_refresh_filename(BlockDriverState *bs, QDict *options)
458
{
459
    BDRVNBDState *s = bs->opaque;
460
    QDict *opts = qdict_new();
461
    const char *port = s->port ?: stringify(NBD_DEFAULT_PORT);
462

M
Max Reitz 已提交
463
    qdict_put(opts, "driver", qstring_from_str("nbd"));
464

465
    if (s->path && s->export) {
466
        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
467 468
                 "nbd+unix:///%s?socket=%s", s->export, s->path);
    } else if (s->path && !s->export) {
469
        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
470
                 "nbd+unix://?socket=%s", s->path);
471
    } else if (!s->path && s->export) {
M
Max Reitz 已提交
472
        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
473 474
                 "nbd://%s:%s/%s", s->host, port, s->export);
    } else if (!s->path && !s->export) {
M
Max Reitz 已提交
475
        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
476
                 "nbd://%s:%s", s->host, port);
M
Max Reitz 已提交
477 478
    }

479
    if (s->path) {
M
Max Reitz 已提交
480
        qdict_put(opts, "path", qstring_from_str(s->path));
M
Max Reitz 已提交
481
    } else {
M
Max Reitz 已提交
482 483
        qdict_put(opts, "host", qstring_from_str(s->host));
        qdict_put(opts, "port", qstring_from_str(port));
M
Max Reitz 已提交
484
    }
485
    if (s->export) {
M
Max Reitz 已提交
486
        qdict_put(opts, "export", qstring_from_str(s->export));
487
    }
488
    if (s->tlscredsid) {
M
Max Reitz 已提交
489
        qdict_put(opts, "tls-creds", qstring_from_str(s->tlscredsid));
490
    }
491 492 493 494

    bs->full_open_options = opts;
}

495
static BlockDriver bdrv_nbd = {
496 497 498 499 500
    .format_name                = "nbd",
    .protocol_name              = "nbd",
    .instance_size              = sizeof(BDRVNBDState),
    .bdrv_parse_filename        = nbd_parse_filename,
    .bdrv_file_open             = nbd_open,
501 502
    .bdrv_co_preadv             = nbd_client_co_preadv,
    .bdrv_co_pwritev            = nbd_client_co_pwritev,
503 504
    .bdrv_close                 = nbd_close,
    .bdrv_co_flush_to_os        = nbd_co_flush,
505
    .bdrv_co_pdiscard           = nbd_client_co_pdiscard,
506
    .bdrv_refresh_limits        = nbd_refresh_limits,
507 508 509
    .bdrv_getlength             = nbd_getlength,
    .bdrv_detach_aio_context    = nbd_detach_aio_context,
    .bdrv_attach_aio_context    = nbd_attach_aio_context,
510
    .bdrv_refresh_filename      = nbd_refresh_filename,
P
Paolo Bonzini 已提交
511 512 513
};

static BlockDriver bdrv_nbd_tcp = {
514 515 516 517 518
    .format_name                = "nbd",
    .protocol_name              = "nbd+tcp",
    .instance_size              = sizeof(BDRVNBDState),
    .bdrv_parse_filename        = nbd_parse_filename,
    .bdrv_file_open             = nbd_open,
519 520
    .bdrv_co_preadv             = nbd_client_co_preadv,
    .bdrv_co_pwritev            = nbd_client_co_pwritev,
521 522
    .bdrv_close                 = nbd_close,
    .bdrv_co_flush_to_os        = nbd_co_flush,
523
    .bdrv_co_pdiscard           = nbd_client_co_pdiscard,
524
    .bdrv_refresh_limits        = nbd_refresh_limits,
525 526 527
    .bdrv_getlength             = nbd_getlength,
    .bdrv_detach_aio_context    = nbd_detach_aio_context,
    .bdrv_attach_aio_context    = nbd_attach_aio_context,
528
    .bdrv_refresh_filename      = nbd_refresh_filename,
P
Paolo Bonzini 已提交
529 530 531
};

static BlockDriver bdrv_nbd_unix = {
532 533 534 535 536
    .format_name                = "nbd",
    .protocol_name              = "nbd+unix",
    .instance_size              = sizeof(BDRVNBDState),
    .bdrv_parse_filename        = nbd_parse_filename,
    .bdrv_file_open             = nbd_open,
537 538
    .bdrv_co_preadv             = nbd_client_co_preadv,
    .bdrv_co_pwritev            = nbd_client_co_pwritev,
539 540
    .bdrv_close                 = nbd_close,
    .bdrv_co_flush_to_os        = nbd_co_flush,
541
    .bdrv_co_pdiscard           = nbd_client_co_pdiscard,
542
    .bdrv_refresh_limits        = nbd_refresh_limits,
543 544 545
    .bdrv_getlength             = nbd_getlength,
    .bdrv_detach_aio_context    = nbd_detach_aio_context,
    .bdrv_attach_aio_context    = nbd_attach_aio_context,
546
    .bdrv_refresh_filename      = nbd_refresh_filename,
547
};
548 549 550 551

static void bdrv_nbd_init(void)
{
    bdrv_register(&bdrv_nbd);
P
Paolo Bonzini 已提交
552 553
    bdrv_register(&bdrv_nbd_tcp);
    bdrv_register(&bdrv_nbd_unix);
554 555 556
}

block_init(bdrv_nbd_init);