qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

blockdev-nbd.c (7892B)


      1 /*
      2  * Serving QEMU block devices via NBD
      3  *
      4  * Copyright (c) 2012 Red Hat, Inc.
      5  *
      6  * Author: Paolo Bonzini <pbonzini@redhat.com>
      7  *
      8  * This work is licensed under the terms of the GNU GPL, version 2 or
      9  * later.  See the COPYING file in the top-level directory.
     10  */
     11 
     12 #include "qemu/osdep.h"
     13 #include "sysemu/blockdev.h"
     14 #include "sysemu/block-backend.h"
     15 #include "hw/block/block.h"
     16 #include "qapi/error.h"
     17 #include "qapi/clone-visitor.h"
     18 #include "qapi/qapi-visit-block-export.h"
     19 #include "qapi/qapi-commands-block-export.h"
     20 #include "block/nbd.h"
     21 #include "io/channel-socket.h"
     22 #include "io/net-listener.h"
     23 
     24 typedef struct NBDServerData {
     25     QIONetListener *listener;
     26     QCryptoTLSCreds *tlscreds;
     27     char *tlsauthz;
     28     uint32_t max_connections;
     29     uint32_t connections;
     30 } NBDServerData;
     31 
     32 static NBDServerData *nbd_server;
     33 static int qemu_nbd_connections = -1; /* Non-negative if this is qemu-nbd */
     34 
     35 static void nbd_update_server_watch(NBDServerData *s);
     36 
     37 void nbd_server_is_qemu_nbd(int max_connections)
     38 {
     39     qemu_nbd_connections = max_connections;
     40 }
     41 
     42 bool nbd_server_is_running(void)
     43 {
     44     return nbd_server || qemu_nbd_connections >= 0;
     45 }
     46 
     47 int nbd_server_max_connections(void)
     48 {
     49     return nbd_server ? nbd_server->max_connections : qemu_nbd_connections;
     50 }
     51 
     52 static void nbd_blockdev_client_closed(NBDClient *client, bool ignored)
     53 {
     54     nbd_client_put(client);
     55     assert(nbd_server->connections > 0);
     56     nbd_server->connections--;
     57     nbd_update_server_watch(nbd_server);
     58 }
     59 
     60 static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
     61                        gpointer opaque)
     62 {
     63     nbd_server->connections++;
     64     nbd_update_server_watch(nbd_server);
     65 
     66     qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server");
     67     nbd_client_new(cioc, nbd_server->tlscreds, nbd_server->tlsauthz,
     68                    nbd_blockdev_client_closed);
     69 }
     70 
     71 static void nbd_update_server_watch(NBDServerData *s)
     72 {
     73     if (!s->max_connections || s->connections < s->max_connections) {
     74         qio_net_listener_set_client_func(s->listener, nbd_accept, NULL, NULL);
     75     } else {
     76         qio_net_listener_set_client_func(s->listener, NULL, NULL, NULL);
     77     }
     78 }
     79 
     80 static void nbd_server_free(NBDServerData *server)
     81 {
     82     if (!server) {
     83         return;
     84     }
     85 
     86     qio_net_listener_disconnect(server->listener);
     87     object_unref(OBJECT(server->listener));
     88     if (server->tlscreds) {
     89         object_unref(OBJECT(server->tlscreds));
     90     }
     91     g_free(server->tlsauthz);
     92 
     93     g_free(server);
     94 }
     95 
     96 static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
     97 {
     98     Object *obj;
     99     QCryptoTLSCreds *creds;
    100 
    101     obj = object_resolve_path_component(
    102         object_get_objects_root(), id);
    103     if (!obj) {
    104         error_setg(errp, "No TLS credentials with id '%s'",
    105                    id);
    106         return NULL;
    107     }
    108     creds = (QCryptoTLSCreds *)
    109         object_dynamic_cast(obj, TYPE_QCRYPTO_TLS_CREDS);
    110     if (!creds) {
    111         error_setg(errp, "Object with id '%s' is not TLS credentials",
    112                    id);
    113         return NULL;
    114     }
    115 
    116     if (!qcrypto_tls_creds_check_endpoint(creds,
    117                                           QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
    118                                           errp)) {
    119         return NULL;
    120     }
    121     object_ref(obj);
    122     return creds;
    123 }
    124 
    125 
    126 void nbd_server_start(SocketAddress *addr, const char *tls_creds,
    127                       const char *tls_authz, uint32_t max_connections,
    128                       Error **errp)
    129 {
    130     if (nbd_server) {
    131         error_setg(errp, "NBD server already running");
    132         return;
    133     }
    134 
    135     nbd_server = g_new0(NBDServerData, 1);
    136     nbd_server->max_connections = max_connections;
    137     nbd_server->listener = qio_net_listener_new();
    138 
    139     qio_net_listener_set_name(nbd_server->listener,
    140                               "nbd-listener");
    141 
    142     /*
    143      * Because this server is persistent, a backlog of SOMAXCONN is
    144      * better than trying to size it to max_connections.
    145      */
    146     if (qio_net_listener_open_sync(nbd_server->listener, addr, SOMAXCONN,
    147                                    errp) < 0) {
    148         goto error;
    149     }
    150 
    151     if (tls_creds) {
    152         nbd_server->tlscreds = nbd_get_tls_creds(tls_creds, errp);
    153         if (!nbd_server->tlscreds) {
    154             goto error;
    155         }
    156     }
    157 
    158     nbd_server->tlsauthz = g_strdup(tls_authz);
    159 
    160     nbd_update_server_watch(nbd_server);
    161 
    162     return;
    163 
    164  error:
    165     nbd_server_free(nbd_server);
    166     nbd_server = NULL;
    167 }
    168 
    169 void nbd_server_start_options(NbdServerOptions *arg, Error **errp)
    170 {
    171     nbd_server_start(arg->addr, arg->tls_creds, arg->tls_authz,
    172                      arg->max_connections, errp);
    173 }
    174 
    175 void qmp_nbd_server_start(SocketAddressLegacy *addr,
    176                           bool has_tls_creds, const char *tls_creds,
    177                           bool has_tls_authz, const char *tls_authz,
    178                           bool has_max_connections, uint32_t max_connections,
    179                           Error **errp)
    180 {
    181     SocketAddress *addr_flat = socket_address_flatten(addr);
    182 
    183     nbd_server_start(addr_flat, tls_creds, tls_authz, max_connections, errp);
    184     qapi_free_SocketAddress(addr_flat);
    185 }
    186 
    187 void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
    188 {
    189     BlockExport *export;
    190     BlockDriverState *bs;
    191     BlockBackend *on_eject_blk;
    192     BlockExportOptions *export_opts;
    193 
    194     bs = bdrv_lookup_bs(arg->device, arg->device, errp);
    195     if (!bs) {
    196         return;
    197     }
    198 
    199     /*
    200      * block-export-add would default to the node-name, but we may have to use
    201      * the device name as a default here for compatibility.
    202      */
    203     if (!arg->has_name) {
    204         arg->has_name = true;
    205         arg->name = g_strdup(arg->device);
    206     }
    207 
    208     export_opts = g_new(BlockExportOptions, 1);
    209     *export_opts = (BlockExportOptions) {
    210         .type                   = BLOCK_EXPORT_TYPE_NBD,
    211         .id                     = g_strdup(arg->name),
    212         .node_name              = g_strdup(bdrv_get_node_name(bs)),
    213         .has_writable           = arg->has_writable,
    214         .writable               = arg->writable,
    215     };
    216     QAPI_CLONE_MEMBERS(BlockExportOptionsNbdBase, &export_opts->u.nbd,
    217                        qapi_NbdServerAddOptions_base(arg));
    218     if (arg->has_bitmap) {
    219         BlockDirtyBitmapOrStr *el = g_new(BlockDirtyBitmapOrStr, 1);
    220 
    221         *el = (BlockDirtyBitmapOrStr) {
    222             .type = QTYPE_QSTRING,
    223             .u.local = g_strdup(arg->bitmap),
    224         };
    225         export_opts->u.nbd.has_bitmaps = true;
    226         QAPI_LIST_PREPEND(export_opts->u.nbd.bitmaps, el);
    227     }
    228 
    229     /*
    230      * nbd-server-add doesn't complain when a read-only device should be
    231      * exported as writable, but simply downgrades it. This is an error with
    232      * block-export-add.
    233      */
    234     if (bdrv_is_read_only(bs)) {
    235         export_opts->has_writable = true;
    236         export_opts->writable = false;
    237     }
    238 
    239     export = blk_exp_add(export_opts, errp);
    240     if (!export) {
    241         goto fail;
    242     }
    243 
    244     /*
    245      * nbd-server-add removes the export when the named BlockBackend used for
    246      * @device goes away.
    247      */
    248     on_eject_blk = blk_by_name(arg->device);
    249     if (on_eject_blk) {
    250         nbd_export_set_on_eject_blk(export, on_eject_blk);
    251     }
    252 
    253 fail:
    254     qapi_free_BlockExportOptions(export_opts);
    255 }
    256 
    257 void qmp_nbd_server_remove(const char *name,
    258                            bool has_mode, BlockExportRemoveMode mode,
    259                            Error **errp)
    260 {
    261     BlockExport *exp;
    262 
    263     exp = blk_exp_find(name);
    264     if (exp && exp->drv->type != BLOCK_EXPORT_TYPE_NBD) {
    265         error_setg(errp, "Block export '%s' is not an NBD export", name);
    266         return;
    267     }
    268 
    269     qmp_block_export_del(name, has_mode, mode, errp);
    270 }
    271 
    272 void qmp_nbd_server_stop(Error **errp)
    273 {
    274     if (!nbd_server) {
    275         error_setg(errp, "NBD server not running");
    276         return;
    277     }
    278 
    279     blk_exp_close_all_type(BLOCK_EXPORT_TYPE_NBD);
    280 
    281     nbd_server_free(nbd_server);
    282     nbd_server = NULL;
    283 }