
Add an option in BlockExportOptions to allow creating an export on an inactive node without activating the node. This mode needs to be explicitly supported by the export type (so that it doesn't perform any operations that are forbidden for inactive nodes), so this patch alone doesn't allow this option to be successfully used yet. Signed-off-by: Kevin Wolf <kwolf@redhat.com> Acked-by: Fabiano Rosas <farosas@suse.de> Reviewed-by: Eric Blake <eblake@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Message-ID: <20250204211407.381505-13-kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
373 lines
9.3 KiB
C
373 lines
9.3 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 "system/block-backend.h"
|
|
#include "system/iothread.h"
|
|
#include "block/export.h"
|
|
#include "block/fuse.h"
|
|
#include "block/nbd.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/qapi-commands-block-export.h"
|
|
#include "qapi/qapi-events-block-export.h"
|
|
#include "qemu/id.h"
|
|
#ifdef CONFIG_VHOST_USER_BLK_SERVER
|
|
#include "vhost-user-blk-server.h"
|
|
#endif
|
|
#ifdef CONFIG_VDUSE_BLK_EXPORT
|
|
#include "vduse-blk.h"
|
|
#endif
|
|
|
|
static const BlockExportDriver *blk_exp_drivers[] = {
|
|
&blk_exp_nbd,
|
|
#ifdef CONFIG_VHOST_USER_BLK_SERVER
|
|
&blk_exp_vhost_user_blk,
|
|
#endif
|
|
#ifdef CONFIG_FUSE
|
|
&blk_exp_fuse,
|
|
#endif
|
|
#ifdef CONFIG_VDUSE_BLK_EXPORT
|
|
&blk_exp_vduse_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)
|
|
{
|
|
bool fixed_iothread = export->has_fixed_iothread && export->fixed_iothread;
|
|
bool allow_inactive = export->has_allow_inactive && export->allow_inactive;
|
|
const BlockExportDriver *drv;
|
|
BlockExport *exp = NULL;
|
|
BlockDriverState *bs;
|
|
BlockBackend *blk = NULL;
|
|
AioContext *ctx;
|
|
uint64_t perm;
|
|
int ret;
|
|
|
|
GLOBAL_STATE_CODE();
|
|
|
|
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);
|
|
|
|
if (export->iothread) {
|
|
IOThread *iothread;
|
|
AioContext *new_ctx;
|
|
Error **set_context_errp;
|
|
|
|
iothread = iothread_by_id(export->iothread);
|
|
if (!iothread) {
|
|
error_setg(errp, "iothread \"%s\" not found", export->iothread);
|
|
goto fail;
|
|
}
|
|
|
|
new_ctx = iothread_get_aio_context(iothread);
|
|
|
|
/* Ignore errors with fixed-iothread=false */
|
|
set_context_errp = fixed_iothread ? errp : NULL;
|
|
ret = bdrv_try_change_aio_context(bs, new_ctx, NULL, set_context_errp);
|
|
if (ret == 0) {
|
|
ctx = new_ctx;
|
|
} else if (fixed_iothread) {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
bdrv_graph_rdlock_main_loop();
|
|
if (allow_inactive) {
|
|
if (!drv->supports_inactive) {
|
|
error_setg(errp, "Export type does not support inactive exports");
|
|
bdrv_graph_rdunlock_main_loop();
|
|
goto fail;
|
|
}
|
|
} else {
|
|
/*
|
|
* 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.
|
|
*/
|
|
ret = bdrv_activate(bs, errp);
|
|
if (ret < 0) {
|
|
bdrv_graph_rdunlock_main_loop();
|
|
goto fail;
|
|
}
|
|
}
|
|
bdrv_graph_rdunlock_main_loop();
|
|
|
|
perm = BLK_PERM_CONSISTENT_READ;
|
|
if (export->writable) {
|
|
perm |= BLK_PERM_WRITE;
|
|
}
|
|
|
|
blk = blk_new(ctx, perm, BLK_PERM_ALL);
|
|
|
|
if (!fixed_iothread) {
|
|
blk_set_allow_aio_context_change(blk, true);
|
|
}
|
|
if (allow_inactive) {
|
|
blk_set_force_allow_inactivate(blk);
|
|
}
|
|
|
|
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);
|
|
return exp;
|
|
|
|
fail:
|
|
if (blk) {
|
|
blk_set_dev_ops(blk, NULL, NULL);
|
|
blk_unref(blk);
|
|
}
|
|
if (exp) {
|
|
g_free(exp->id);
|
|
g_free(exp);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void blk_exp_ref(BlockExport *exp)
|
|
{
|
|
assert(qatomic_read(&exp->refcount) > 0);
|
|
qatomic_inc(&exp->refcount);
|
|
}
|
|
|
|
/* Runs in the main thread */
|
|
static void blk_exp_delete_bh(void *opaque)
|
|
{
|
|
BlockExport *exp = opaque;
|
|
|
|
assert(exp->refcount == 0);
|
|
QLIST_REMOVE(exp, next);
|
|
exp->drv->delete(exp);
|
|
blk_set_dev_ops(exp->blk, NULL, NULL);
|
|
blk_unref(exp->blk);
|
|
qapi_event_send_block_export_deleted(exp->id);
|
|
g_free(exp->id);
|
|
g_free(exp);
|
|
}
|
|
|
|
void blk_exp_unref(BlockExport *exp)
|
|
{
|
|
assert(qatomic_read(&exp->refcount) > 0);
|
|
if (qatomic_fetch_dec(&exp->refcount) == 1) {
|
|
/* 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.
|
|
*/
|
|
void blk_exp_request_shutdown(BlockExport *exp)
|
|
{
|
|
/*
|
|
* 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) {
|
|
return;
|
|
}
|
|
|
|
exp->drv->request_shutdown(exp);
|
|
|
|
assert(exp->user_owned);
|
|
exp->user_owned = false;
|
|
blk_exp_unref(exp);
|
|
}
|
|
|
|
/*
|
|
* 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_UNLOCKED(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 &&
|
|
qatomic_read(&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, **tail = &head;
|
|
BlockExport *exp;
|
|
|
|
QLIST_FOREACH(exp, &block_exports, next) {
|
|
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,
|
|
};
|
|
|
|
QAPI_LIST_APPEND(tail, info);
|
|
}
|
|
|
|
return head;
|
|
}
|