mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-11-02 21:32:52 +00:00
80dd5aff1b
'blockdev-change-medium' is a convinient wrapper for the following sequence of commands: * blockdev-open-tray * blockdev-remove-medium * blockdev-insert-medium * blockdev-close-tray and should be used f.e. to change ISO image inside the CD-ROM tray. Though the guest could lock the tray and some linux guests like CentOS 8.5 actually does that. In this case the execution if this command results in the error like the following: Device 'scsi0-0-1-0' is locked and force was not specified, wait for tray to open and try again. This situation is could be resolved 'blockdev-open-tray' by passing flag 'force' inside. Thus is seems reasonable to add the same capability for 'blockdev-change-medium' too. Signed-off-by: Denis V. Lunev <den@openvz.org> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@openvz.org> Acked-by: "Dr. David Alan Gilbert" <dgilbert@redhat.com> CC: Kevin Wolf <kwolf@redhat.com> CC: Hanna Reitz <hreitz@redhat.com> CC: Eric Blake <eblake@redhat.com> CC: Markus Armbruster <armbru@redhat.com> Message-Id: <20220412221846.280723-1-den@openvz.org> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Signed-off-by: Hanna Reitz <hreitz@redhat.com>
591 lines
17 KiB
C
591 lines
17 KiB
C
/*
|
|
* QMP command handlers specific to the system emulators
|
|
*
|
|
* Copyright (c) 2003-2008 Fabrice Bellard
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or
|
|
* later. See the COPYING file in the top-level directory.
|
|
*
|
|
* This file incorporates work covered by the following copyright and
|
|
* permission notice:
|
|
*
|
|
* Copyright (c) 2003-2008 Fabrice Bellard
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include "qapi/error.h"
|
|
#include "qapi/qapi-commands-block.h"
|
|
#include "qapi/qmp/qdict.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "sysemu/blockdev.h"
|
|
|
|
static BlockBackend *qmp_get_blk(const char *blk_name, const char *qdev_id,
|
|
Error **errp)
|
|
{
|
|
BlockBackend *blk;
|
|
|
|
if (!blk_name == !qdev_id) {
|
|
error_setg(errp, "Need exactly one of 'device' and 'id'");
|
|
return NULL;
|
|
}
|
|
|
|
if (qdev_id) {
|
|
blk = blk_by_qdev_id(qdev_id, errp);
|
|
} else {
|
|
blk = blk_by_name(blk_name);
|
|
if (blk == NULL) {
|
|
error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
|
|
"Device '%s' not found", blk_name);
|
|
}
|
|
}
|
|
|
|
return blk;
|
|
}
|
|
|
|
/*
|
|
* Attempt to open the tray of @device.
|
|
* If @force, ignore its tray lock.
|
|
* Else, if the tray is locked, don't open it, but ask the guest to open it.
|
|
* On error, store an error through @errp and return -errno.
|
|
* If @device does not exist, return -ENODEV.
|
|
* If it has no removable media, return -ENOTSUP.
|
|
* If it has no tray, return -ENOSYS.
|
|
* If the guest was asked to open the tray, return -EINPROGRESS.
|
|
* Else, return 0.
|
|
*/
|
|
static int do_open_tray(const char *blk_name, const char *qdev_id,
|
|
bool force, Error **errp)
|
|
{
|
|
BlockBackend *blk;
|
|
const char *device = qdev_id ?: blk_name;
|
|
bool locked;
|
|
|
|
blk = qmp_get_blk(blk_name, qdev_id, errp);
|
|
if (!blk) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!blk_dev_has_removable_media(blk)) {
|
|
error_setg(errp, "Device '%s' is not removable", device);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (!blk_dev_has_tray(blk)) {
|
|
error_setg(errp, "Device '%s' does not have a tray", device);
|
|
return -ENOSYS;
|
|
}
|
|
|
|
if (blk_dev_is_tray_open(blk)) {
|
|
return 0;
|
|
}
|
|
|
|
locked = blk_dev_is_medium_locked(blk);
|
|
if (locked) {
|
|
blk_dev_eject_request(blk, force);
|
|
}
|
|
|
|
if (!locked || force) {
|
|
blk_dev_change_media_cb(blk, false, &error_abort);
|
|
}
|
|
|
|
if (locked && !force) {
|
|
error_setg(errp, "Device '%s' is locked and force was not specified, "
|
|
"wait for tray to open and try again", device);
|
|
return -EINPROGRESS;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void qmp_blockdev_open_tray(bool has_device, const char *device,
|
|
bool has_id, const char *id,
|
|
bool has_force, bool force,
|
|
Error **errp)
|
|
{
|
|
Error *local_err = NULL;
|
|
int rc;
|
|
|
|
if (!has_force) {
|
|
force = false;
|
|
}
|
|
rc = do_open_tray(has_device ? device : NULL,
|
|
has_id ? id : NULL,
|
|
force, &local_err);
|
|
if (rc && rc != -ENOSYS && rc != -EINPROGRESS) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
error_free(local_err);
|
|
}
|
|
|
|
void qmp_blockdev_close_tray(bool has_device, const char *device,
|
|
bool has_id, const char *id,
|
|
Error **errp)
|
|
{
|
|
BlockBackend *blk;
|
|
Error *local_err = NULL;
|
|
|
|
device = has_device ? device : NULL;
|
|
id = has_id ? id : NULL;
|
|
|
|
blk = qmp_get_blk(device, id, errp);
|
|
if (!blk) {
|
|
return;
|
|
}
|
|
|
|
if (!blk_dev_has_removable_media(blk)) {
|
|
error_setg(errp, "Device '%s' is not removable", device ?: id);
|
|
return;
|
|
}
|
|
|
|
if (!blk_dev_has_tray(blk)) {
|
|
/* Ignore this command on tray-less devices */
|
|
return;
|
|
}
|
|
|
|
if (!blk_dev_is_tray_open(blk)) {
|
|
return;
|
|
}
|
|
|
|
blk_dev_change_media_cb(blk, true, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void blockdev_remove_medium(bool has_device, const char *device,
|
|
bool has_id, const char *id, Error **errp)
|
|
{
|
|
BlockBackend *blk;
|
|
BlockDriverState *bs;
|
|
AioContext *aio_context;
|
|
bool has_attached_device;
|
|
|
|
device = has_device ? device : NULL;
|
|
id = has_id ? id : NULL;
|
|
|
|
blk = qmp_get_blk(device, id, errp);
|
|
if (!blk) {
|
|
return;
|
|
}
|
|
|
|
/* For BBs without a device, we can exchange the BDS tree at will */
|
|
has_attached_device = blk_get_attached_dev(blk);
|
|
|
|
if (has_attached_device && !blk_dev_has_removable_media(blk)) {
|
|
error_setg(errp, "Device '%s' is not removable", device ?: id);
|
|
return;
|
|
}
|
|
|
|
if (has_attached_device && blk_dev_has_tray(blk) &&
|
|
!blk_dev_is_tray_open(blk))
|
|
{
|
|
error_setg(errp, "Tray of device '%s' is not open", device ?: id);
|
|
return;
|
|
}
|
|
|
|
bs = blk_bs(blk);
|
|
if (!bs) {
|
|
return;
|
|
}
|
|
|
|
aio_context = bdrv_get_aio_context(bs);
|
|
aio_context_acquire(aio_context);
|
|
|
|
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_EJECT, errp)) {
|
|
goto out;
|
|
}
|
|
|
|
blk_remove_bs(blk);
|
|
|
|
if (!blk_dev_has_tray(blk)) {
|
|
/* For tray-less devices, blockdev-open-tray is a no-op (or may not be
|
|
* called at all); therefore, the medium needs to be ejected here.
|
|
* Do it after blk_remove_bs() so blk_is_inserted(blk) returns the @load
|
|
* value passed here (i.e. false). */
|
|
blk_dev_change_media_cb(blk, false, &error_abort);
|
|
}
|
|
|
|
out:
|
|
aio_context_release(aio_context);
|
|
}
|
|
|
|
void qmp_blockdev_remove_medium(const char *id, Error **errp)
|
|
{
|
|
blockdev_remove_medium(false, NULL, true, id, errp);
|
|
}
|
|
|
|
static void qmp_blockdev_insert_anon_medium(BlockBackend *blk,
|
|
BlockDriverState *bs, Error **errp)
|
|
{
|
|
Error *local_err = NULL;
|
|
bool has_device;
|
|
int ret;
|
|
|
|
/* For BBs without a device, we can exchange the BDS tree at will */
|
|
has_device = blk_get_attached_dev(blk);
|
|
|
|
if (has_device && !blk_dev_has_removable_media(blk)) {
|
|
error_setg(errp, "Device is not removable");
|
|
return;
|
|
}
|
|
|
|
if (has_device && blk_dev_has_tray(blk) && !blk_dev_is_tray_open(blk)) {
|
|
error_setg(errp, "Tray of the device is not open");
|
|
return;
|
|
}
|
|
|
|
if (blk_bs(blk)) {
|
|
error_setg(errp, "There already is a medium in the device");
|
|
return;
|
|
}
|
|
|
|
ret = blk_insert_bs(blk, bs, errp);
|
|
if (ret < 0) {
|
|
return;
|
|
}
|
|
|
|
if (!blk_dev_has_tray(blk)) {
|
|
/* For tray-less devices, blockdev-close-tray is a no-op (or may not be
|
|
* called at all); therefore, the medium needs to be pushed into the
|
|
* slot here.
|
|
* Do it after blk_insert_bs() so blk_is_inserted(blk) returns the @load
|
|
* value passed here (i.e. true). */
|
|
blk_dev_change_media_cb(blk, true, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
blk_remove_bs(blk);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void blockdev_insert_medium(bool has_device, const char *device,
|
|
bool has_id, const char *id,
|
|
const char *node_name, Error **errp)
|
|
{
|
|
BlockBackend *blk;
|
|
BlockDriverState *bs;
|
|
|
|
blk = qmp_get_blk(has_device ? device : NULL,
|
|
has_id ? id : NULL,
|
|
errp);
|
|
if (!blk) {
|
|
return;
|
|
}
|
|
|
|
bs = bdrv_find_node(node_name);
|
|
if (!bs) {
|
|
error_setg(errp, "Node '%s' not found", node_name);
|
|
return;
|
|
}
|
|
|
|
if (bdrv_has_blk(bs)) {
|
|
error_setg(errp, "Node '%s' is already in use", node_name);
|
|
return;
|
|
}
|
|
|
|
qmp_blockdev_insert_anon_medium(blk, bs, errp);
|
|
}
|
|
|
|
void qmp_blockdev_insert_medium(const char *id, const char *node_name,
|
|
Error **errp)
|
|
{
|
|
blockdev_insert_medium(false, NULL, true, id, node_name, errp);
|
|
}
|
|
|
|
void qmp_blockdev_change_medium(bool has_device, const char *device,
|
|
bool has_id, const char *id,
|
|
const char *filename,
|
|
bool has_format, const char *format,
|
|
bool has_force, bool force,
|
|
bool has_read_only,
|
|
BlockdevChangeReadOnlyMode read_only,
|
|
Error **errp)
|
|
{
|
|
BlockBackend *blk;
|
|
BlockDriverState *medium_bs = NULL;
|
|
int bdrv_flags;
|
|
bool detect_zeroes;
|
|
int rc;
|
|
QDict *options = NULL;
|
|
Error *err = NULL;
|
|
|
|
blk = qmp_get_blk(has_device ? device : NULL,
|
|
has_id ? id : NULL,
|
|
errp);
|
|
if (!blk) {
|
|
goto fail;
|
|
}
|
|
|
|
if (blk_bs(blk)) {
|
|
blk_update_root_state(blk);
|
|
}
|
|
|
|
bdrv_flags = blk_get_open_flags_from_root_state(blk);
|
|
bdrv_flags &= ~(BDRV_O_TEMPORARY | BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING |
|
|
BDRV_O_PROTOCOL | BDRV_O_AUTO_RDONLY);
|
|
|
|
if (!has_read_only) {
|
|
read_only = BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN;
|
|
}
|
|
|
|
switch (read_only) {
|
|
case BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN:
|
|
break;
|
|
|
|
case BLOCKDEV_CHANGE_READ_ONLY_MODE_READ_ONLY:
|
|
bdrv_flags &= ~BDRV_O_RDWR;
|
|
break;
|
|
|
|
case BLOCKDEV_CHANGE_READ_ONLY_MODE_READ_WRITE:
|
|
bdrv_flags |= BDRV_O_RDWR;
|
|
break;
|
|
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
options = qdict_new();
|
|
detect_zeroes = blk_get_detect_zeroes_from_root_state(blk);
|
|
qdict_put_str(options, "detect-zeroes", detect_zeroes ? "on" : "off");
|
|
|
|
if (has_format) {
|
|
qdict_put_str(options, "driver", format);
|
|
}
|
|
|
|
medium_bs = bdrv_open(filename, NULL, options, bdrv_flags, errp);
|
|
if (!medium_bs) {
|
|
goto fail;
|
|
}
|
|
|
|
rc = do_open_tray(has_device ? device : NULL,
|
|
has_id ? id : NULL,
|
|
force, &err);
|
|
if (rc && rc != -ENOSYS) {
|
|
error_propagate(errp, err);
|
|
goto fail;
|
|
}
|
|
error_free(err);
|
|
err = NULL;
|
|
|
|
blockdev_remove_medium(has_device, device, has_id, id, &err);
|
|
if (err) {
|
|
error_propagate(errp, err);
|
|
goto fail;
|
|
}
|
|
|
|
qmp_blockdev_insert_anon_medium(blk, medium_bs, &err);
|
|
if (err) {
|
|
error_propagate(errp, err);
|
|
goto fail;
|
|
}
|
|
|
|
qmp_blockdev_close_tray(has_device, device, has_id, id, errp);
|
|
|
|
fail:
|
|
/* If the medium has been inserted, the device has its own reference, so
|
|
* ours must be relinquished; and if it has not been inserted successfully,
|
|
* the reference must be relinquished anyway */
|
|
bdrv_unref(medium_bs);
|
|
}
|
|
|
|
void qmp_eject(bool has_device, const char *device,
|
|
bool has_id, const char *id,
|
|
bool has_force, bool force, Error **errp)
|
|
{
|
|
Error *local_err = NULL;
|
|
int rc;
|
|
|
|
if (!has_force) {
|
|
force = false;
|
|
}
|
|
|
|
rc = do_open_tray(has_device ? device : NULL,
|
|
has_id ? id : NULL,
|
|
force, &local_err);
|
|
if (rc && rc != -ENOSYS) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
error_free(local_err);
|
|
|
|
blockdev_remove_medium(has_device, device, has_id, id, errp);
|
|
}
|
|
|
|
/* throttling disk I/O limits */
|
|
void qmp_block_set_io_throttle(BlockIOThrottle *arg, Error **errp)
|
|
{
|
|
ThrottleConfig cfg;
|
|
BlockDriverState *bs;
|
|
BlockBackend *blk;
|
|
AioContext *aio_context;
|
|
|
|
blk = qmp_get_blk(arg->has_device ? arg->device : NULL,
|
|
arg->has_id ? arg->id : NULL,
|
|
errp);
|
|
if (!blk) {
|
|
return;
|
|
}
|
|
|
|
aio_context = blk_get_aio_context(blk);
|
|
aio_context_acquire(aio_context);
|
|
|
|
bs = blk_bs(blk);
|
|
if (!bs) {
|
|
error_setg(errp, "Device has no medium");
|
|
goto out;
|
|
}
|
|
|
|
throttle_config_init(&cfg);
|
|
cfg.buckets[THROTTLE_BPS_TOTAL].avg = arg->bps;
|
|
cfg.buckets[THROTTLE_BPS_READ].avg = arg->bps_rd;
|
|
cfg.buckets[THROTTLE_BPS_WRITE].avg = arg->bps_wr;
|
|
|
|
cfg.buckets[THROTTLE_OPS_TOTAL].avg = arg->iops;
|
|
cfg.buckets[THROTTLE_OPS_READ].avg = arg->iops_rd;
|
|
cfg.buckets[THROTTLE_OPS_WRITE].avg = arg->iops_wr;
|
|
|
|
if (arg->has_bps_max) {
|
|
cfg.buckets[THROTTLE_BPS_TOTAL].max = arg->bps_max;
|
|
}
|
|
if (arg->has_bps_rd_max) {
|
|
cfg.buckets[THROTTLE_BPS_READ].max = arg->bps_rd_max;
|
|
}
|
|
if (arg->has_bps_wr_max) {
|
|
cfg.buckets[THROTTLE_BPS_WRITE].max = arg->bps_wr_max;
|
|
}
|
|
if (arg->has_iops_max) {
|
|
cfg.buckets[THROTTLE_OPS_TOTAL].max = arg->iops_max;
|
|
}
|
|
if (arg->has_iops_rd_max) {
|
|
cfg.buckets[THROTTLE_OPS_READ].max = arg->iops_rd_max;
|
|
}
|
|
if (arg->has_iops_wr_max) {
|
|
cfg.buckets[THROTTLE_OPS_WRITE].max = arg->iops_wr_max;
|
|
}
|
|
|
|
if (arg->has_bps_max_length) {
|
|
cfg.buckets[THROTTLE_BPS_TOTAL].burst_length = arg->bps_max_length;
|
|
}
|
|
if (arg->has_bps_rd_max_length) {
|
|
cfg.buckets[THROTTLE_BPS_READ].burst_length = arg->bps_rd_max_length;
|
|
}
|
|
if (arg->has_bps_wr_max_length) {
|
|
cfg.buckets[THROTTLE_BPS_WRITE].burst_length = arg->bps_wr_max_length;
|
|
}
|
|
if (arg->has_iops_max_length) {
|
|
cfg.buckets[THROTTLE_OPS_TOTAL].burst_length = arg->iops_max_length;
|
|
}
|
|
if (arg->has_iops_rd_max_length) {
|
|
cfg.buckets[THROTTLE_OPS_READ].burst_length = arg->iops_rd_max_length;
|
|
}
|
|
if (arg->has_iops_wr_max_length) {
|
|
cfg.buckets[THROTTLE_OPS_WRITE].burst_length = arg->iops_wr_max_length;
|
|
}
|
|
|
|
if (arg->has_iops_size) {
|
|
cfg.op_size = arg->iops_size;
|
|
}
|
|
|
|
if (!throttle_is_valid(&cfg, errp)) {
|
|
goto out;
|
|
}
|
|
|
|
if (throttle_enabled(&cfg)) {
|
|
/* Enable I/O limits if they're not enabled yet, otherwise
|
|
* just update the throttling group. */
|
|
if (!blk_get_public(blk)->throttle_group_member.throttle_state) {
|
|
blk_io_limits_enable(blk,
|
|
arg->has_group ? arg->group :
|
|
arg->has_device ? arg->device :
|
|
arg->id);
|
|
} else if (arg->has_group) {
|
|
blk_io_limits_update_group(blk, arg->group);
|
|
}
|
|
/* Set the new throttling configuration */
|
|
blk_set_io_limits(blk, &cfg);
|
|
} else if (blk_get_public(blk)->throttle_group_member.throttle_state) {
|
|
/* If all throttling settings are set to 0, disable I/O limits */
|
|
blk_io_limits_disable(blk);
|
|
}
|
|
|
|
out:
|
|
aio_context_release(aio_context);
|
|
}
|
|
|
|
void qmp_block_latency_histogram_set(
|
|
const char *id,
|
|
bool has_boundaries, uint64List *boundaries,
|
|
bool has_boundaries_read, uint64List *boundaries_read,
|
|
bool has_boundaries_write, uint64List *boundaries_write,
|
|
bool has_boundaries_flush, uint64List *boundaries_flush,
|
|
Error **errp)
|
|
{
|
|
BlockBackend *blk = qmp_get_blk(NULL, id, errp);
|
|
BlockAcctStats *stats;
|
|
int ret;
|
|
|
|
if (!blk) {
|
|
return;
|
|
}
|
|
|
|
stats = blk_get_stats(blk);
|
|
|
|
if (!has_boundaries && !has_boundaries_read && !has_boundaries_write &&
|
|
!has_boundaries_flush)
|
|
{
|
|
block_latency_histograms_clear(stats);
|
|
return;
|
|
}
|
|
|
|
if (has_boundaries || has_boundaries_read) {
|
|
ret = block_latency_histogram_set(
|
|
stats, BLOCK_ACCT_READ,
|
|
has_boundaries_read ? boundaries_read : boundaries);
|
|
if (ret) {
|
|
error_setg(errp, "Device '%s' set read boundaries fail", id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (has_boundaries || has_boundaries_write) {
|
|
ret = block_latency_histogram_set(
|
|
stats, BLOCK_ACCT_WRITE,
|
|
has_boundaries_write ? boundaries_write : boundaries);
|
|
if (ret) {
|
|
error_setg(errp, "Device '%s' set write boundaries fail", id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (has_boundaries || has_boundaries_flush) {
|
|
ret = block_latency_histogram_set(
|
|
stats, BLOCK_ACCT_FLUSH,
|
|
has_boundaries_flush ? boundaries_flush : boundaries);
|
|
if (ret) {
|
|
error_setg(errp, "Device '%s' set flush boundaries fail", id);
|
|
return;
|
|
}
|
|
}
|
|
}
|