Use the new QAPI block exports API instead of defining our own QOM objects. This is a large change because the lifecycle of VuBlockDev needs to follow BlockExportDriver. QOM properties are replaced by QAPI options objects. VuBlockDev is renamed VuBlkExport and contains a BlockExport field. Several fields can be dropped since BlockExport already has equivalents. The file names and meson build integration will be adjusted in a future patch. libvhost-user should probably be built as a static library that is linked into QEMU instead of as a .c file that results in duplicate compilation. The new command-line syntax is: $ qemu-storage-daemon \ --blockdev file,node-name=drive0,filename=test.img \ --export vhost-user-blk,node-name=drive0,id=export0,unix-socket=/tmp/vhost-user-blk.sock Note that unix-socket is optional because we may wish to accept chardevs too in the future. Markus noted that supported address families are not explicit in the QAPI schema. It is unlikely that support for more address families will be added since file descriptor passing is required and few address families support it. If a new address family needs to be added, then the QAPI 'features' syntax can be used to advertize them. Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> Acked-by: Markus Armbruster <armbru@redhat.com> Message-id: 20200924151549.913737-12-stefanha@redhat.com [Skip test on big-endian host architectures because this device doesn't support them yet (as already mentioned in a code comment). --Stefan] Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
332 lines
8 KiB
C
332 lines
8 KiB
C
/*
|
|
* Common block export infrastructure
|
|
*
|
|
* Copyright (c) 2012, 2020 Red Hat, Inc.
|
|
*
|
|
* Authors:
|
|
* Paolo Bonzini <pbonzini@redhat.com>
|
|
* Kevin Wolf <kwolf@redhat.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or
|
|
* later. See the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include "block/block.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "block/export.h"
|
|
#include "block/nbd.h"
|
|
#if CONFIG_LINUX
|
|
#include "block/export/vhost-user-blk-server.h"
|
|
#endif
|
|
#include "qapi/error.h"
|
|
#include "qapi/qapi-commands-block-export.h"
|
|
#include "qapi/qapi-events-block-export.h"
|
|
#include "qemu/id.h"
|
|
|
|
static const BlockExportDriver *blk_exp_drivers[] = {
|
|
&blk_exp_nbd,
|
|
#if CONFIG_LINUX
|
|
&blk_exp_vhost_user_blk,
|
|
#endif
|
|
};
|
|
|
|
/* Only accessed from the main thread */
|
|
static QLIST_HEAD(, BlockExport) block_exports =
|
|
QLIST_HEAD_INITIALIZER(block_exports);
|
|
|
|
BlockExport *blk_exp_find(const char *id)
|
|
{
|
|
BlockExport *exp;
|
|
|
|
QLIST_FOREACH(exp, &block_exports, next) {
|
|
if (strcmp(id, exp->id) == 0) {
|
|
return exp;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const BlockExportDriver *blk_exp_find_driver(BlockExportType type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(blk_exp_drivers); i++) {
|
|
if (blk_exp_drivers[i]->type == type) {
|
|
return blk_exp_drivers[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
|
|
{
|
|
const BlockExportDriver *drv;
|
|
BlockExport *exp = NULL;
|
|
BlockDriverState *bs;
|
|
BlockBackend *blk;
|
|
AioContext *ctx;
|
|
uint64_t perm;
|
|
int ret;
|
|
|
|
if (!id_wellformed(export->id)) {
|
|
error_setg(errp, "Invalid block export id");
|
|
return NULL;
|
|
}
|
|
if (blk_exp_find(export->id)) {
|
|
error_setg(errp, "Block export id '%s' is already in use", export->id);
|
|
return NULL;
|
|
}
|
|
|
|
drv = blk_exp_find_driver(export->type);
|
|
if (!drv) {
|
|
error_setg(errp, "No driver found for the requested export type");
|
|
return NULL;
|
|
}
|
|
|
|
bs = bdrv_lookup_bs(NULL, export->node_name, errp);
|
|
if (!bs) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!export->has_writable) {
|
|
export->writable = false;
|
|
}
|
|
if (bdrv_is_read_only(bs) && export->writable) {
|
|
error_setg(errp, "Cannot export read-only node as writable");
|
|
return NULL;
|
|
}
|
|
|
|
ctx = bdrv_get_aio_context(bs);
|
|
aio_context_acquire(ctx);
|
|
|
|
/*
|
|
* Block exports are used for non-shared storage migration. Make sure
|
|
* that BDRV_O_INACTIVE is cleared and the image is ready for write
|
|
* access since the export could be available before migration handover.
|
|
* ctx was acquired in the caller.
|
|
*/
|
|
bdrv_invalidate_cache(bs, NULL);
|
|
|
|
perm = BLK_PERM_CONSISTENT_READ;
|
|
if (export->writable) {
|
|
perm |= BLK_PERM_WRITE;
|
|
}
|
|
|
|
blk = blk_new(ctx, perm, BLK_PERM_ALL);
|
|
ret = blk_insert_bs(blk, bs, errp);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
|
|
if (!export->has_writethrough) {
|
|
export->writethrough = false;
|
|
}
|
|
blk_set_enable_write_cache(blk, !export->writethrough);
|
|
|
|
assert(drv->instance_size >= sizeof(BlockExport));
|
|
exp = g_malloc0(drv->instance_size);
|
|
*exp = (BlockExport) {
|
|
.drv = drv,
|
|
.refcount = 1,
|
|
.user_owned = true,
|
|
.id = g_strdup(export->id),
|
|
.ctx = ctx,
|
|
.blk = blk,
|
|
};
|
|
|
|
ret = drv->create(exp, export, errp);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
|
|
assert(exp->blk != NULL);
|
|
|
|
QLIST_INSERT_HEAD(&block_exports, exp, next);
|
|
|
|
aio_context_release(ctx);
|
|
return exp;
|
|
|
|
fail:
|
|
blk_unref(blk);
|
|
aio_context_release(ctx);
|
|
if (exp) {
|
|
g_free(exp->id);
|
|
g_free(exp);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Callers must hold exp->ctx lock */
|
|
void blk_exp_ref(BlockExport *exp)
|
|
{
|
|
assert(exp->refcount > 0);
|
|
exp->refcount++;
|
|
}
|
|
|
|
/* Runs in the main thread */
|
|
static void blk_exp_delete_bh(void *opaque)
|
|
{
|
|
BlockExport *exp = opaque;
|
|
AioContext *aio_context = exp->ctx;
|
|
|
|
aio_context_acquire(aio_context);
|
|
|
|
assert(exp->refcount == 0);
|
|
QLIST_REMOVE(exp, next);
|
|
exp->drv->delete(exp);
|
|
blk_unref(exp->blk);
|
|
qapi_event_send_block_export_deleted(exp->id);
|
|
g_free(exp->id);
|
|
g_free(exp);
|
|
|
|
aio_context_release(aio_context);
|
|
}
|
|
|
|
/* Callers must hold exp->ctx lock */
|
|
void blk_exp_unref(BlockExport *exp)
|
|
{
|
|
assert(exp->refcount > 0);
|
|
if (--exp->refcount == 0) {
|
|
/* Touch the block_exports list only in the main thread */
|
|
aio_bh_schedule_oneshot(qemu_get_aio_context(), blk_exp_delete_bh,
|
|
exp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Drops the user reference to the export and requests that all client
|
|
* connections and other internally held references start to shut down. When
|
|
* the function returns, there may still be active references while the export
|
|
* is in the process of shutting down.
|
|
*
|
|
* Acquires exp->ctx internally. Callers must *not* hold the lock.
|
|
*/
|
|
void blk_exp_request_shutdown(BlockExport *exp)
|
|
{
|
|
AioContext *aio_context = exp->ctx;
|
|
|
|
aio_context_acquire(aio_context);
|
|
|
|
/*
|
|
* If the user doesn't own the export any more, it is already shutting
|
|
* down. We must not call .request_shutdown and decrease the refcount a
|
|
* second time.
|
|
*/
|
|
if (!exp->user_owned) {
|
|
goto out;
|
|
}
|
|
|
|
exp->drv->request_shutdown(exp);
|
|
|
|
assert(exp->user_owned);
|
|
exp->user_owned = false;
|
|
blk_exp_unref(exp);
|
|
|
|
out:
|
|
aio_context_release(aio_context);
|
|
}
|
|
|
|
/*
|
|
* Returns whether a block export of the given type exists.
|
|
* type == BLOCK_EXPORT_TYPE__MAX checks for an export of any type.
|
|
*/
|
|
static bool blk_exp_has_type(BlockExportType type)
|
|
{
|
|
BlockExport *exp;
|
|
|
|
if (type == BLOCK_EXPORT_TYPE__MAX) {
|
|
return !QLIST_EMPTY(&block_exports);
|
|
}
|
|
|
|
QLIST_FOREACH(exp, &block_exports, next) {
|
|
if (exp->drv->type == type) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* type == BLOCK_EXPORT_TYPE__MAX for all types */
|
|
void blk_exp_close_all_type(BlockExportType type)
|
|
{
|
|
BlockExport *exp, *next;
|
|
|
|
assert(in_aio_context_home_thread(qemu_get_aio_context()));
|
|
|
|
QLIST_FOREACH_SAFE(exp, &block_exports, next, next) {
|
|
if (type != BLOCK_EXPORT_TYPE__MAX && exp->drv->type != type) {
|
|
continue;
|
|
}
|
|
blk_exp_request_shutdown(exp);
|
|
}
|
|
|
|
AIO_WAIT_WHILE(NULL, blk_exp_has_type(type));
|
|
}
|
|
|
|
void blk_exp_close_all(void)
|
|
{
|
|
blk_exp_close_all_type(BLOCK_EXPORT_TYPE__MAX);
|
|
}
|
|
|
|
void qmp_block_export_add(BlockExportOptions *export, Error **errp)
|
|
{
|
|
blk_exp_add(export, errp);
|
|
}
|
|
|
|
void qmp_block_export_del(const char *id,
|
|
bool has_mode, BlockExportRemoveMode mode,
|
|
Error **errp)
|
|
{
|
|
ERRP_GUARD();
|
|
BlockExport *exp;
|
|
|
|
exp = blk_exp_find(id);
|
|
if (exp == NULL) {
|
|
error_setg(errp, "Export '%s' is not found", id);
|
|
return;
|
|
}
|
|
if (!exp->user_owned) {
|
|
error_setg(errp, "Export '%s' is already shutting down", id);
|
|
return;
|
|
}
|
|
|
|
if (!has_mode) {
|
|
mode = BLOCK_EXPORT_REMOVE_MODE_SAFE;
|
|
}
|
|
if (mode == BLOCK_EXPORT_REMOVE_MODE_SAFE && exp->refcount > 1) {
|
|
error_setg(errp, "export '%s' still in use", exp->id);
|
|
error_append_hint(errp, "Use mode='hard' to force client "
|
|
"disconnect\n");
|
|
return;
|
|
}
|
|
|
|
blk_exp_request_shutdown(exp);
|
|
}
|
|
|
|
BlockExportInfoList *qmp_query_block_exports(Error **errp)
|
|
{
|
|
BlockExportInfoList *head = NULL, **p_next = &head;
|
|
BlockExport *exp;
|
|
|
|
QLIST_FOREACH(exp, &block_exports, next) {
|
|
BlockExportInfoList *entry = g_new0(BlockExportInfoList, 1);
|
|
BlockExportInfo *info = g_new(BlockExportInfo, 1);
|
|
*info = (BlockExportInfo) {
|
|
.id = g_strdup(exp->id),
|
|
.type = exp->drv->type,
|
|
.node_name = g_strdup(bdrv_get_node_name(blk_bs(exp->blk))),
|
|
.shutting_down = !exp->user_owned,
|
|
};
|
|
|
|
entry->value = info;
|
|
*p_next = entry;
|
|
p_next = &entry->next;
|
|
}
|
|
|
|
return head;
|
|
}
|