| /* | |
| * 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; | |
| bool multithread = export->iothread && | |
| export->iothread->type == QTYPE_QLIST; | |
| const BlockExportDriver *drv; | |
| BlockExport *exp = NULL; | |
| BlockDriverState *bs; | |
| BlockBackend *blk = NULL; | |
| AioContext *ctx; | |
| AioContext **multithread_ctxs = NULL; | |
| size_t multithread_count = 0; | |
| uint64_t perm; | |
| int ret; | |
| GLOBAL_STATE_CODE(); | |
| if (fixed_iothread && multithread) { | |
| error_setg(errp, | |
| "Cannot use fixed-iothread for a multi-threaded export"); | |
| return NULL; | |
| } | |
| 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); | |
| /* Move the BDS to the target I/O thread, if it is a single one */ | |
| if (export->iothread && !multithread) { | |
| const char *iothread_id = export->iothread->u.single; | |
| IOThread *iothread; | |
| AioContext *new_ctx; | |
| Error **set_context_errp; | |
| iothread = iothread_by_id(iothread_id); | |
| if (!iothread) { | |
| error_setg(errp, "iothread \"%s\" not found", iothread_id); | |
| 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; | |
| } | |
| } else if (multithread) { | |
| strList *iothread_list = export->iothread->u.multi; | |
| size_t i; | |
| multithread_count = 0; | |
| for (strList *e = iothread_list; e; e = e->next) { | |
| multithread_count++; | |
| } | |
| if (multithread_count == 0) { | |
| error_setg(errp, "The set of I/O threads must not be empty"); | |
| return NULL; | |
| } | |
| multithread_ctxs = g_new(AioContext *, multithread_count); | |
| i = 0; | |
| for (strList *e = iothread_list; e; e = e->next) { | |
| IOThread *iothread = iothread_by_id(e->value); | |
| if (!iothread) { | |
| error_setg(errp, "iothread \"%s\" not found", e->value); | |
| goto fail; | |
| } | |
| multithread_ctxs[i++] = iothread_get_aio_context(iothread); | |
| } | |
| assert(i == multithread_count); | |
| } | |
| 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, multithread_ctxs, multithread_count, errp); | |
| if (ret < 0) { | |
| goto fail; | |
| } | |
| assert(exp->blk != NULL); | |
| QLIST_INSERT_HEAD(&block_exports, exp, next); | |
| g_free(multithread_ctxs); | |
| return exp; | |
| fail: | |
| if (blk) { | |
| blk_set_dev_ops(blk, NULL, NULL); | |
| blk_unref(blk); | |
| } | |
| if (exp) { | |
| g_free(exp->id); | |
| g_free(exp); | |
| } | |
| g_free(multithread_ctxs); | |
| 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; | |
| } |