|  | /* | 
|  | * QEMU block throttling filter driver infrastructure | 
|  | * | 
|  | * Copyright (c) 2017 Manos Pitsidianakis | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU General Public License as | 
|  | * published by the Free Software Foundation; either version 2 or | 
|  | * (at your option) version 3 of the License. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | * GNU General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU General Public License | 
|  | * along with this program; if not, see <http://www.gnu.org/licenses/>. | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  | #include "block/block-io.h" | 
|  | #include "block/block_int.h" | 
|  | #include "block/throttle-groups.h" | 
|  | #include "qemu/module.h" | 
|  | #include "qemu/option.h" | 
|  | #include "qemu/throttle-options.h" | 
|  | #include "qapi/error.h" | 
|  |  | 
|  | static QemuOptsList throttle_opts = { | 
|  | .name = "throttle", | 
|  | .head = QTAILQ_HEAD_INITIALIZER(throttle_opts.head), | 
|  | .desc = { | 
|  | { | 
|  | .name = QEMU_OPT_THROTTLE_GROUP_NAME, | 
|  | .type = QEMU_OPT_STRING, | 
|  | .help = "Name of the throttle group", | 
|  | }, | 
|  | { /* end of list */ } | 
|  | }, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * If this function succeeds then the throttle group name is stored in | 
|  | * @group and must be freed by the caller. | 
|  | * If there's an error then @group remains unmodified. | 
|  | */ | 
|  | static int throttle_parse_options(QDict *options, char **group, Error **errp) | 
|  | { | 
|  | int ret; | 
|  | const char *group_name; | 
|  | QemuOpts *opts = qemu_opts_create(&throttle_opts, NULL, 0, &error_abort); | 
|  |  | 
|  | if (!qemu_opts_absorb_qdict(opts, options, errp)) { | 
|  | ret = -EINVAL; | 
|  | goto fin; | 
|  | } | 
|  |  | 
|  | group_name = qemu_opt_get(opts, QEMU_OPT_THROTTLE_GROUP_NAME); | 
|  | if (!group_name) { | 
|  | error_setg(errp, "Please specify a throttle group"); | 
|  | ret = -EINVAL; | 
|  | goto fin; | 
|  | } else if (!throttle_group_exists(group_name)) { | 
|  | error_setg(errp, "Throttle group '%s' does not exist", group_name); | 
|  | ret = -EINVAL; | 
|  | goto fin; | 
|  | } | 
|  |  | 
|  | *group = g_strdup(group_name); | 
|  | ret = 0; | 
|  | fin: | 
|  | qemu_opts_del(opts); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int throttle_open(BlockDriverState *bs, QDict *options, | 
|  | int flags, Error **errp) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | char *group; | 
|  | int ret; | 
|  |  | 
|  | ret = bdrv_open_file_child(NULL, options, "file", bs, errp); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | GRAPH_RDLOCK_GUARD_MAINLOOP(); | 
|  |  | 
|  | bs->supported_write_flags = bs->file->bs->supported_write_flags | | 
|  | BDRV_REQ_WRITE_UNCHANGED; | 
|  | bs->supported_zero_flags = bs->file->bs->supported_zero_flags | | 
|  | BDRV_REQ_WRITE_UNCHANGED; | 
|  |  | 
|  | ret = throttle_parse_options(options, &group, errp); | 
|  | if (ret == 0) { | 
|  | /* Register membership to group with name group_name */ | 
|  | throttle_group_register_tgm(tgm, group, bdrv_get_aio_context(bs)); | 
|  | g_free(group); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void throttle_close(BlockDriverState *bs) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | throttle_group_unregister_tgm(tgm); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int64_t coroutine_fn GRAPH_RDLOCK | 
|  | throttle_co_getlength(BlockDriverState *bs) | 
|  | { | 
|  | return bdrv_co_getlength(bs->file->bs); | 
|  | } | 
|  |  | 
|  | static int coroutine_fn GRAPH_RDLOCK | 
|  | throttle_co_preadv(BlockDriverState *bs, int64_t offset, int64_t bytes, | 
|  | QEMUIOVector *qiov, BdrvRequestFlags flags) | 
|  | { | 
|  |  | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | throttle_group_co_io_limits_intercept(tgm, bytes, THROTTLE_READ); | 
|  |  | 
|  | return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags); | 
|  | } | 
|  |  | 
|  | static int coroutine_fn GRAPH_RDLOCK | 
|  | throttle_co_pwritev(BlockDriverState *bs, int64_t offset, int64_t bytes, | 
|  | QEMUIOVector *qiov, BdrvRequestFlags flags) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | throttle_group_co_io_limits_intercept(tgm, bytes, THROTTLE_WRITE); | 
|  |  | 
|  | return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags); | 
|  | } | 
|  |  | 
|  | static int coroutine_fn GRAPH_RDLOCK | 
|  | throttle_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int64_t bytes, | 
|  | BdrvRequestFlags flags) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | throttle_group_co_io_limits_intercept(tgm, bytes, THROTTLE_WRITE); | 
|  |  | 
|  | return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags); | 
|  | } | 
|  |  | 
|  | static int coroutine_fn GRAPH_RDLOCK | 
|  | throttle_co_pdiscard(BlockDriverState *bs, int64_t offset, int64_t bytes) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | throttle_group_co_io_limits_intercept(tgm, bytes, THROTTLE_WRITE); | 
|  |  | 
|  | return bdrv_co_pdiscard(bs->file, offset, bytes); | 
|  | } | 
|  |  | 
|  | static int coroutine_fn GRAPH_RDLOCK | 
|  | throttle_co_pwritev_compressed(BlockDriverState *bs, int64_t offset, | 
|  | int64_t bytes, QEMUIOVector *qiov) | 
|  | { | 
|  | return throttle_co_pwritev(bs, offset, bytes, qiov, | 
|  | BDRV_REQ_WRITE_COMPRESSED); | 
|  | } | 
|  |  | 
|  | static int coroutine_fn GRAPH_RDLOCK throttle_co_flush(BlockDriverState *bs) | 
|  | { | 
|  | return bdrv_co_flush(bs->file->bs); | 
|  | } | 
|  |  | 
|  | static void throttle_detach_aio_context(BlockDriverState *bs) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | throttle_group_detach_aio_context(tgm); | 
|  | } | 
|  |  | 
|  | static void throttle_attach_aio_context(BlockDriverState *bs, | 
|  | AioContext *new_context) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | throttle_group_attach_aio_context(tgm, new_context); | 
|  | } | 
|  |  | 
|  | static int throttle_reopen_prepare(BDRVReopenState *reopen_state, | 
|  | BlockReopenQueue *queue, Error **errp) | 
|  | { | 
|  | int ret; | 
|  | char *group = NULL; | 
|  |  | 
|  | assert(reopen_state != NULL); | 
|  | assert(reopen_state->bs != NULL); | 
|  |  | 
|  | ret = throttle_parse_options(reopen_state->options, &group, errp); | 
|  | reopen_state->opaque = group; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void throttle_reopen_commit(BDRVReopenState *reopen_state) | 
|  | { | 
|  | BlockDriverState *bs = reopen_state->bs; | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | char *group = reopen_state->opaque; | 
|  |  | 
|  | assert(group); | 
|  |  | 
|  | if (strcmp(group, throttle_group_get_name(tgm))) { | 
|  | throttle_group_unregister_tgm(tgm); | 
|  | throttle_group_register_tgm(tgm, group, bdrv_get_aio_context(bs)); | 
|  | } | 
|  | g_free(reopen_state->opaque); | 
|  | reopen_state->opaque = NULL; | 
|  | } | 
|  |  | 
|  | static void throttle_reopen_abort(BDRVReopenState *reopen_state) | 
|  | { | 
|  | g_free(reopen_state->opaque); | 
|  | reopen_state->opaque = NULL; | 
|  | } | 
|  |  | 
|  | static void throttle_drain_begin(BlockDriverState *bs) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | if (qatomic_fetch_inc(&tgm->io_limits_disabled) == 0) { | 
|  | throttle_group_restart_tgm(tgm); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void throttle_drain_end(BlockDriverState *bs) | 
|  | { | 
|  | ThrottleGroupMember *tgm = bs->opaque; | 
|  | assert(tgm->io_limits_disabled); | 
|  | qatomic_dec(&tgm->io_limits_disabled); | 
|  | } | 
|  |  | 
|  | static const char *const throttle_strong_runtime_opts[] = { | 
|  | QEMU_OPT_THROTTLE_GROUP_NAME, | 
|  |  | 
|  | NULL | 
|  | }; | 
|  |  | 
|  | static BlockDriver bdrv_throttle = { | 
|  | .format_name                        =   "throttle", | 
|  | .instance_size                      =   sizeof(ThrottleGroupMember), | 
|  |  | 
|  | .bdrv_open                          =   throttle_open, | 
|  | .bdrv_close                         =   throttle_close, | 
|  | .bdrv_co_flush                      =   throttle_co_flush, | 
|  |  | 
|  | .bdrv_child_perm                    =   bdrv_default_perms, | 
|  |  | 
|  | .bdrv_co_getlength                  =   throttle_co_getlength, | 
|  |  | 
|  | .bdrv_co_preadv                     =   throttle_co_preadv, | 
|  | .bdrv_co_pwritev                    =   throttle_co_pwritev, | 
|  |  | 
|  | .bdrv_co_pwrite_zeroes              =   throttle_co_pwrite_zeroes, | 
|  | .bdrv_co_pdiscard                   =   throttle_co_pdiscard, | 
|  | .bdrv_co_pwritev_compressed         =   throttle_co_pwritev_compressed, | 
|  |  | 
|  | .bdrv_attach_aio_context            =   throttle_attach_aio_context, | 
|  | .bdrv_detach_aio_context            =   throttle_detach_aio_context, | 
|  |  | 
|  | .bdrv_reopen_prepare                =   throttle_reopen_prepare, | 
|  | .bdrv_reopen_commit                 =   throttle_reopen_commit, | 
|  | .bdrv_reopen_abort                  =   throttle_reopen_abort, | 
|  |  | 
|  | .bdrv_drain_begin                   =   throttle_drain_begin, | 
|  | .bdrv_drain_end                     =   throttle_drain_end, | 
|  |  | 
|  | .is_filter                          =   true, | 
|  | .strong_runtime_opts                =   throttle_strong_runtime_opts, | 
|  | }; | 
|  |  | 
|  | static void bdrv_throttle_init(void) | 
|  | { | 
|  | bdrv_register(&bdrv_throttle); | 
|  | } | 
|  |  | 
|  | block_init(bdrv_throttle_init); |