* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
#include "sysemu/block-backend.h"
#include "sysemu/blockdev.h"
#include "hw/block/block.h"
#include "trace.h"
#include "sysemu/arch_init.h"
+static QTAILQ_HEAD(, BlockDriverState) monitor_bdrv_states =
+ QTAILQ_HEAD_INITIALIZER(monitor_bdrv_states);
+
static const char *const if_name[IF_COUNT] = {
[IF_NONE] = "none",
[IF_IDE] = "ide",
DriveInfo *dinfo = blk_legacy_dinfo(blk);
if (dinfo && dinfo->auto_del) {
+ monitor_remove_blk(blk);
blk_unref(blk);
}
}
return true;
}
-static bool check_throttle_config(ThrottleConfig *cfg, Error **errp)
-{
- if (throttle_conflicting(cfg)) {
- error_setg(errp, "bps/iops/max total values and read/write values"
- " cannot be used at the same time");
- return false;
- }
-
- if (!throttle_is_valid(cfg)) {
- error_setg(errp, "bps/iops/maxs values must be 0 or greater");
- return false;
- }
-
- if (throttle_max_is_missing_limit(cfg)) {
- error_setg(errp, "bps_max/iops_max require corresponding"
- " bps/iops values");
- return false;
- }
-
- return true;
-}
-
typedef enum { MEDIA_DISK, MEDIA_CDROM } DriveMediaType;
/* All parameters but @opts are optional and may be set to NULL. */
}
if (throttle_cfg) {
- memset(throttle_cfg, 0, sizeof(*throttle_cfg));
+ throttle_config_init(throttle_cfg);
throttle_cfg->buckets[THROTTLE_BPS_TOTAL].avg =
qemu_opt_get_number(opts, "throttling.bps-total", 0);
throttle_cfg->buckets[THROTTLE_BPS_READ].avg =
throttle_cfg->buckets[THROTTLE_OPS_WRITE].max =
qemu_opt_get_number(opts, "throttling.iops-write-max", 0);
+ throttle_cfg->buckets[THROTTLE_BPS_TOTAL].burst_length =
+ qemu_opt_get_number(opts, "throttling.bps-total-max-length", 1);
+ throttle_cfg->buckets[THROTTLE_BPS_READ].burst_length =
+ qemu_opt_get_number(opts, "throttling.bps-read-max-length", 1);
+ throttle_cfg->buckets[THROTTLE_BPS_WRITE].burst_length =
+ qemu_opt_get_number(opts, "throttling.bps-write-max-length", 1);
+ throttle_cfg->buckets[THROTTLE_OPS_TOTAL].burst_length =
+ qemu_opt_get_number(opts, "throttling.iops-total-max-length", 1);
+ throttle_cfg->buckets[THROTTLE_OPS_READ].burst_length =
+ qemu_opt_get_number(opts, "throttling.iops-read-max-length", 1);
+ throttle_cfg->buckets[THROTTLE_OPS_WRITE].burst_length =
+ qemu_opt_get_number(opts, "throttling.iops-write-max-length", 1);
+
throttle_cfg->op_size =
qemu_opt_get_number(opts, "throttling.iops-size", 0);
- if (!check_throttle_config(throttle_cfg, errp)) {
+ if (!throttle_is_valid(throttle_cfg, errp)) {
return;
}
}
if ((!file || !*file) && !qdict_size(bs_opts)) {
BlockBackendRootState *blk_rs;
- blk = blk_new(qemu_opts_id(opts), errp);
+ blk = blk_new(errp);
if (!blk) {
goto early_err;
}
qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_DIRECT, "off");
qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_NO_FLUSH, "off");
- if (snapshot) {
- /* always use cache=unsafe with snapshot */
- qdict_put(bs_opts, BDRV_OPT_CACHE_WB, qstring_from_str("on"));
- qdict_put(bs_opts, BDRV_OPT_CACHE_DIRECT, qstring_from_str("off"));
- qdict_put(bs_opts, BDRV_OPT_CACHE_NO_FLUSH, qstring_from_str("on"));
+ if (runstate_check(RUN_STATE_INMIGRATE)) {
+ bdrv_flags |= BDRV_O_INACTIVE;
}
- blk = blk_new_open(qemu_opts_id(opts), file, NULL, bs_opts, bdrv_flags,
- errp);
+ blk = blk_new_open(file, NULL, bs_opts, bdrv_flags, errp);
if (!blk) {
goto err_no_bs_opts;
}
blk_set_on_error(blk, on_read_error, on_write_error);
+ if (!monitor_add_blk(blk, qemu_opts_id(opts), errp)) {
+ blk_unref(blk);
+ blk = NULL;
+ goto err_no_bs_opts;
+ }
+
err_no_bs_opts:
qemu_opts_del(opts);
QDECREF(interval_dict);
goto fail;
}
+ /* bdrv_open() defaults to the values in bdrv_flags (for compatibility
+ * with other callers) rather than what we want as the real defaults.
+ * Apply the defaults here instead. */
+ qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_WB, "on");
+ qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_DIRECT, "off");
+ qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_NO_FLUSH, "off");
+
+ if (runstate_check(RUN_STATE_INMIGRATE)) {
+ bdrv_flags |= BDRV_O_INACTIVE;
+ }
+
bs = NULL;
ret = bdrv_open(&bs, NULL, NULL, bs_opts, bdrv_flags, errp);
if (ret < 0) {
return NULL;
}
+void blockdev_close_all_bdrv_states(void)
+{
+ BlockDriverState *bs, *next_bs;
+
+ QTAILQ_FOREACH_SAFE(bs, &monitor_bdrv_states, monitor_list, next_bs) {
+ AioContext *ctx = bdrv_get_aio_context(bs);
+
+ aio_context_acquire(ctx);
+ bdrv_unref(bs);
+ aio_context_release(ctx);
+ }
+}
+
+/* Iterates over the list of monitor-owned BlockDriverStates */
+BlockDriverState *bdrv_next_monitor_owned(BlockDriverState *bs)
+{
+ return bs ? QTAILQ_NEXT(bs, monitor_list)
+ : QTAILQ_FIRST(&monitor_bdrv_states);
+}
+
static void qemu_opt_rename(QemuOpts *opts, const char *from, const char *to,
Error **errp)
{
int ret;
if (!strcmp(device, "all")) {
- ret = bdrv_commit_all();
+ ret = blk_commit_all();
} else {
BlockDriverState *bs;
AioContext *aio_context;
}
}
-static void blockdev_do_action(TransactionActionKind type, void *data,
- Error **errp)
+static void blockdev_do_action(TransactionAction *action, Error **errp)
{
- TransactionAction action;
TransactionActionList list;
- action.type = type;
- action.u.data = data;
- list.value = &action;
+ list.value = action;
list.next = NULL;
qmp_transaction(&list, false, NULL, errp);
}
.has_mode = has_mode,
.mode = mode,
};
- blockdev_do_action(TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC,
- &snapshot, errp);
+ TransactionAction action = {
+ .type = TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC,
+ .u.blockdev_snapshot_sync.data = &snapshot,
+ };
+ blockdev_do_action(&action, errp);
}
void qmp_blockdev_snapshot(const char *node, const char *overlay,
.node = (char *) node,
.overlay = (char *) overlay
};
-
- blockdev_do_action(TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT,
- &snapshot_data, errp);
+ TransactionAction action = {
+ .type = TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT,
+ .u.blockdev_snapshot.data = &snapshot_data,
+ };
+ blockdev_do_action(&action, errp);
}
void qmp_blockdev_snapshot_internal_sync(const char *device,
.device = (char *) device,
.name = (char *) name
};
-
- blockdev_do_action(TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC,
- &snapshot, errp);
+ TransactionAction action = {
+ .type = TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC,
+ .u.blockdev_snapshot_internal_sync.data = &snapshot,
+ };
+ blockdev_do_action(&action, errp);
}
SnapshotInfo *qmp_blockdev_snapshot_delete_internal_sync(const char *device,
g_assert(common->action->type ==
TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC);
- internal = common->action->u.blockdev_snapshot_internal_sync;
+ internal = common->action->u.blockdev_snapshot_internal_sync.data;
state = DO_UPCAST(InternalSnapshotState, common, common);
/* 1. parse input */
}
if (bdrv_snapshot_delete(bs, sn->id_str, sn->name, &local_error) < 0) {
- error_report("Failed to delete snapshot with id '%s' and name '%s' on "
- "device '%s' in abort: %s",
- sn->id_str,
- sn->name,
- bdrv_get_device_name(bs),
- error_get_pretty(local_error));
- error_free(local_error);
+ error_reportf_err(local_error,
+ "Failed to delete snapshot with id '%s' and "
+ "name '%s' on device '%s' in abort: ",
+ sn->id_str, sn->name,
+ bdrv_get_device_name(bs));
}
}
switch (action->type) {
case TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT:
{
- BlockdevSnapshot *s = action->u.blockdev_snapshot;
+ BlockdevSnapshot *s = action->u.blockdev_snapshot.data;
device = s->node;
node_name = s->node;
new_image_file = NULL;
break;
case TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC:
{
- BlockdevSnapshotSync *s = action->u.blockdev_snapshot_sync;
+ BlockdevSnapshotSync *s = action->u.blockdev_snapshot_sync.data;
device = s->has_device ? s->device : NULL;
node_name = s->has_node_name ? s->node_name : NULL;
new_image_file = s->snapshot_file;
}
if (action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC) {
- BlockdevSnapshotSync *s = action->u.blockdev_snapshot_sync;
+ BlockdevSnapshotSync *s = action->u.blockdev_snapshot_sync.data;
const char *format = s->has_format ? s->format : "qcow2";
enum NewImageMode mode;
const char *snapshot_node_name =
/* create new image w/backing file */
mode = s->has_mode ? s->mode : NEW_IMAGE_MODE_ABSOLUTE_PATHS;
if (mode != NEW_IMAGE_MODE_EXISTING) {
+ int64_t size = bdrv_getlength(state->old_bs);
+ if (size < 0) {
+ error_setg_errno(errp, -size, "bdrv_getlength failed");
+ return;
+ }
bdrv_img_create(new_image_file, format,
state->old_bs->filename,
state->old_bs->drv->format_name,
- NULL, -1, flags, &local_err, false);
+ NULL, size, flags, &local_err, false);
if (local_err) {
error_propagate(errp, local_err);
return;
Error *local_err = NULL;
assert(common->action->type == TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
- backup = common->action->u.drive_backup;
+ backup = common->action->u.drive_backup.data;
blk = blk_by_name(backup->device);
if (!blk) {
Error *local_err = NULL;
assert(common->action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP);
- backup = common->action->u.blockdev_backup;
+ backup = common->action->u.blockdev_backup.data;
blk = blk_by_name(backup->device);
if (!blk) {
return;
}
- action = common->action->u.block_dirty_bitmap_add;
+ action = common->action->u.block_dirty_bitmap_add.data;
/* AIO context taken and released within qmp_block_dirty_bitmap_add */
qmp_block_dirty_bitmap_add(action->node, action->name,
action->has_granularity, action->granularity,
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
common, common);
- action = common->action->u.block_dirty_bitmap_add;
+ action = common->action->u.block_dirty_bitmap_add.data;
/* Should not be able to fail: IF the bitmap was added via .prepare(),
* then the node reference and bitmap name must have been valid.
*/
return;
}
- action = common->action->u.block_dirty_bitmap_clear;
+ action = common->action->u.block_dirty_bitmap_clear.data;
state->bitmap = block_dirty_bitmap_lookup(action->node,
action->name,
&state->bs,
return;
}
+ if (!blk_dev_has_tray(blk)) {
+ /* Ignore this command on tray-less devices */
+ return;
+ }
+
if (blk_dev_is_tray_open(blk)) {
return;
}
return;
}
+ if (!blk_dev_has_tray(blk)) {
+ /* Ignore this command on tray-less devices */
+ return;
+ }
+
if (!blk_dev_is_tray_open(blk)) {
return;
}
return;
}
- if (has_device && !blk_dev_is_tray_open(blk)) {
+ if (has_device && blk_dev_has_tray(blk) && !blk_dev_is_tray_open(blk)) {
error_setg(errp, "Tray of device '%s' is not open", device);
return;
}
goto out;
}
- /* This follows the convention established by bdrv_make_anon() */
- if (bs->device_list.tqe_prev) {
- QTAILQ_REMOVE(&bdrv_states, bs, device_list);
- bs->device_list.tqe_prev = NULL;
- }
-
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);
+ }
+
out:
aio_context_release(aio_context);
}
return;
}
- if (has_device && !blk_dev_is_tray_open(blk)) {
+ if (has_device && blk_dev_has_tray(blk) && !blk_dev_is_tray_open(blk)) {
error_setg(errp, "Tray of device '%s' is not open", device);
return;
}
blk_insert_bs(blk, bs);
- QTAILQ_INSERT_TAIL(&bdrv_states, bs, device_list);
+ 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);
+ }
}
void qmp_x_blockdev_insert_medium(const char *device, const char *node_name,
}
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);
if (!has_read_only) {
read_only = BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN;
int64_t iops_rd_max,
bool has_iops_wr_max,
int64_t iops_wr_max,
+ bool has_bps_max_length,
+ int64_t bps_max_length,
+ bool has_bps_rd_max_length,
+ int64_t bps_rd_max_length,
+ bool has_bps_wr_max_length,
+ int64_t bps_wr_max_length,
+ bool has_iops_max_length,
+ int64_t iops_max_length,
+ bool has_iops_rd_max_length,
+ int64_t iops_rd_max_length,
+ bool has_iops_wr_max_length,
+ int64_t iops_wr_max_length,
bool has_iops_size,
int64_t iops_size,
bool has_group,
goto out;
}
- memset(&cfg, 0, sizeof(cfg));
+ throttle_config_init(&cfg);
cfg.buckets[THROTTLE_BPS_TOTAL].avg = bps;
cfg.buckets[THROTTLE_BPS_READ].avg = bps_rd;
cfg.buckets[THROTTLE_BPS_WRITE].avg = bps_wr;
cfg.buckets[THROTTLE_OPS_WRITE].max = iops_wr_max;
}
+ if (has_bps_max_length) {
+ cfg.buckets[THROTTLE_BPS_TOTAL].burst_length = bps_max_length;
+ }
+ if (has_bps_rd_max_length) {
+ cfg.buckets[THROTTLE_BPS_READ].burst_length = bps_rd_max_length;
+ }
+ if (has_bps_wr_max_length) {
+ cfg.buckets[THROTTLE_BPS_WRITE].burst_length = bps_wr_max_length;
+ }
+ if (has_iops_max_length) {
+ cfg.buckets[THROTTLE_OPS_TOTAL].burst_length = iops_max_length;
+ }
+ if (has_iops_rd_max_length) {
+ cfg.buckets[THROTTLE_OPS_READ].burst_length = iops_rd_max_length;
+ }
+ if (has_iops_wr_max_length) {
+ cfg.buckets[THROTTLE_OPS_WRITE].burst_length = iops_wr_max_length;
+ }
+
if (has_iops_size) {
cfg.op_size = iops_size;
}
- if (!check_throttle_config(&cfg, errp)) {
+ if (!throttle_is_valid(&cfg, errp)) {
goto out;
}
AioContext *aio_context;
Error *local_err = NULL;
+ bs = bdrv_find_node(id);
+ if (bs) {
+ qmp_x_blockdev_del(false, NULL, true, id, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ }
+ return;
+ }
+
blk = blk_by_name(id);
if (!blk) {
error_report("Device '%s' not found", id);
return;
}
- bdrv_close(bs);
+ blk_remove_bs(blk);
}
- /* if we have a device attached to this BlockDriverState
- * then we need to make the drive anonymous until the device
- * can be removed. If this is a drive with no device backing
- * then we can just get rid of the block driver state right here.
+ /* Make the BlockBackend and the attached BlockDriverState anonymous */
+ monitor_remove_blk(blk);
+ if (blk_bs(blk)) {
+ bdrv_make_anon(blk_bs(blk));
+ }
+
+ /* If this BlockBackend has a device attached to it, its refcount will be
+ * decremented when the device is removed; otherwise we have to do so here.
*/
if (blk_get_attached_dev(blk)) {
- blk_hide_on_behalf_of_hmp_drive_del(blk);
/* Further I/O must not pause the guest */
blk_set_on_error(blk, BLOCKDEV_ON_ERROR_REPORT,
BLOCKDEV_ON_ERROR_REPORT);
aio_context_release(aio_context);
}
+void hmp_drive_add_node(Monitor *mon, const char *optstr)
+{
+ QemuOpts *opts;
+ QDict *qdict;
+ Error *local_err = NULL;
+
+ opts = qemu_opts_parse_noisily(&qemu_drive_opts, optstr, false);
+ if (!opts) {
+ return;
+ }
+
+ qdict = qemu_opts_to_qdict(opts, NULL);
+
+ if (!qdict_get_try_str(qdict, "node-name")) {
+ QDECREF(qdict);
+ error_report("'node-name' needs to be specified");
+ goto out;
+ }
+
+ BlockDriverState *bs = bds_tree_init(qdict, &local_err);
+ if (!bs) {
+ error_report_err(local_err);
+ goto out;
+ }
+
+ QTAILQ_INSERT_TAIL(&monitor_bdrv_states, bs, monitor_list);
+
+out:
+ qemu_opts_del(opts);
+}
+
void qmp_blockdev_add(BlockdevOptions *options, Error **errp)
{
QmpOutputVisitor *ov = qmp_output_visitor_new();
}
}
- visit_type_BlockdevOptions(qmp_output_get_visitor(ov),
- &options, NULL, &local_err);
+ visit_type_BlockdevOptions(qmp_output_get_visitor(ov), NULL, &options,
+ &local_err);
if (local_err) {
error_propagate(errp, local_err);
goto fail;
if (!bs) {
goto fail;
}
+
+ QTAILQ_INSERT_TAIL(&monitor_bdrv_states, bs, monitor_list);
}
if (bs && bdrv_key_required(bs)) {
if (blk) {
+ monitor_remove_blk(blk);
blk_unref(blk);
} else {
+ QTAILQ_REMOVE(&monitor_bdrv_states, bs, monitor_list);
bdrv_unref(bs);
}
error_setg(errp, "blockdev-add doesn't support encrypted devices");
}
if (has_id) {
+ /* blk_by_name() never returns a BB that is not owned by the monitor */
blk = blk_by_name(id);
if (!blk) {
error_setg(errp, "Cannot find block backend %s", id);
goto out;
}
- if (bs->refcnt > 1 || !QLIST_EMPTY(&bs->parents)) {
+ if (!blk && !bs->monitor_list.tqe_prev) {
+ error_setg(errp, "Node %s is not owned by the monitor",
+ bs->node_name);
+ goto out;
+ }
+
+ if (bs->refcnt > 1) {
error_setg(errp, "Block device %s is in use",
bdrv_get_device_or_node_name(bs));
goto out;
}
if (blk) {
+ monitor_remove_blk(blk);
blk_unref(blk);
} else {
+ QTAILQ_REMOVE(&monitor_bdrv_states, bs, monitor_list);
bdrv_unref(bs);
}
.name = "throttling.bps-write-max",
.type = QEMU_OPT_NUMBER,
.help = "total bytes write burst",
+ },{
+ .name = "throttling.iops-total-max-length",
+ .type = QEMU_OPT_NUMBER,
+ .help = "length of the iops-total-max burst period, in seconds",
+ },{
+ .name = "throttling.iops-read-max-length",
+ .type = QEMU_OPT_NUMBER,
+ .help = "length of the iops-read-max burst period, in seconds",
+ },{
+ .name = "throttling.iops-write-max-length",
+ .type = QEMU_OPT_NUMBER,
+ .help = "length of the iops-write-max burst period, in seconds",
+ },{
+ .name = "throttling.bps-total-max-length",
+ .type = QEMU_OPT_NUMBER,
+ .help = "length of the bps-total-max burst period, in seconds",
+ },{
+ .name = "throttling.bps-read-max-length",
+ .type = QEMU_OPT_NUMBER,
+ .help = "length of the bps-read-max burst period, in seconds",
+ },{
+ .name = "throttling.bps-write-max-length",
+ .type = QEMU_OPT_NUMBER,
+ .help = "length of the bps-write-max burst period, in seconds",
},{
.name = "throttling.iops-size",
.type = QEMU_OPT_NUMBER,
static QemuOptsList qemu_root_bds_opts = {
.name = "root-bds",
- .head = QTAILQ_HEAD_INITIALIZER(qemu_common_drive_opts.head),
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_root_bds_opts.head),
.desc = {
{
.name = "discard",