diff options
Diffstat (limited to 'blockdev.c')
-rw-r--r-- | blockdev.c | 348 |
1 files changed, 274 insertions, 74 deletions
diff --git a/blockdev.c b/blockdev.c index 5dd01ea14..48bd9a37b 100644 --- a/blockdev.c +++ b/blockdev.c @@ -34,7 +34,6 @@ #include "hw/block/block.h" #include "block/blockjob.h" #include "monitor/monitor.h" -#include "qapi/qmp/qerror.h" #include "qemu/option.h" #include "qemu/config-file.h" #include "qapi/qmp/types.h" @@ -107,7 +106,7 @@ void blockdev_auto_del(BlockDriverState *bs) DriveInfo *dinfo = drive_get_by_blockdev(bs); if (dinfo && dinfo->auto_del) { - drive_put_ref(dinfo); + drive_del(dinfo); } } @@ -214,7 +213,7 @@ static void bdrv_format_print(void *opaque, const char *name) error_printf(" %s", name); } -static void drive_uninit(DriveInfo *dinfo) +void drive_del(DriveInfo *dinfo) { if (dinfo->opts) { qemu_opts_del(dinfo->opts); @@ -227,19 +226,6 @@ static void drive_uninit(DriveInfo *dinfo) g_free(dinfo); } -void drive_put_ref(DriveInfo *dinfo) -{ - assert(dinfo->refcount); - if (--dinfo->refcount == 0) { - drive_uninit(dinfo); - } -} - -void drive_get_ref(DriveInfo *dinfo) -{ - dinfo->refcount++; -} - typedef struct { QEMUBH *bh; BlockDriverState *bs; @@ -288,6 +274,25 @@ static int parse_block_error_action(const char *buf, bool is_read, Error **errp) } } +static inline int parse_enum_option(const char *lookup[], const char *buf, + int max, int def, Error **errp) +{ + int i; + + if (!buf) { + return def; + } + + for (i = 0; i < max; i++) { + if (!strcmp(buf, lookup[i])) { + return i; + } + } + + error_setg(errp, "invalid parameter value: %s", buf); + return def; +} + static bool check_throttle_config(ThrottleConfig *cfg, Error **errp) { if (throttle_conflicting(cfg)) { @@ -311,7 +316,6 @@ static DriveInfo *blockdev_init(const char *file, QDict *bs_opts, Error **errp) { const char *buf; - const char *serial; int ro = 0; int bdrv_flags = 0; int on_read_error, on_write_error; @@ -324,6 +328,7 @@ static DriveInfo *blockdev_init(const char *file, QDict *bs_opts, QemuOpts *opts; const char *id; bool has_driver_specific_opts; + BlockdevDetectZeroesOptions detect_zeroes; BlockDriver *drv = NULL; /* Check common options by copying from bs_opts to opts, all other options @@ -332,7 +337,7 @@ static DriveInfo *blockdev_init(const char *file, QDict *bs_opts, opts = qemu_opts_create(&qemu_common_drive_opts, id, 1, &error); if (error) { error_propagate(errp, error); - return NULL; + goto err_no_opts; } qemu_opts_absorb_qdict(opts, bs_opts, &error); @@ -352,8 +357,6 @@ static DriveInfo *blockdev_init(const char *file, QDict *bs_opts, ro = qemu_opt_get_bool(opts, "read-only", 0); copy_on_read = qemu_opt_get_bool(opts, "copy-on-read", false); - serial = qemu_opt_get(opts, "serial"); - if ((buf = qemu_opt_get(opts, "discard")) != NULL) { if (bdrv_parse_discard_flags(buf, &bdrv_flags) != 0) { error_setg(errp, "invalid discard option"); @@ -452,22 +455,35 @@ static DriveInfo *blockdev_init(const char *file, QDict *bs_opts, } } - if (bdrv_find_node(qemu_opts_id(opts))) { - error_setg(errp, "device id=%s is conflicting with a node-name", - qemu_opts_id(opts)); + detect_zeroes = + parse_enum_option(BlockdevDetectZeroesOptions_lookup, + qemu_opt_get(opts, "detect-zeroes"), + BLOCKDEV_DETECT_ZEROES_OPTIONS_MAX, + BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF, + &error); + if (error) { + error_propagate(errp, error); + goto early_err; + } + + if (detect_zeroes == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP && + !(bdrv_flags & BDRV_O_UNMAP)) { + error_setg(errp, "setting detect-zeroes to unmap is not allowed " + "without setting discard operation to unmap"); goto early_err; } /* init */ dinfo = g_malloc0(sizeof(*dinfo)); dinfo->id = g_strdup(qemu_opts_id(opts)); - dinfo->bdrv = bdrv_new(dinfo->id); + dinfo->bdrv = bdrv_new(dinfo->id, &error); + if (error) { + error_propagate(errp, error); + goto bdrv_new_err; + } dinfo->bdrv->open_flags = snapshot ? BDRV_O_SNAPSHOT : 0; dinfo->bdrv->read_only = ro; - dinfo->refcount = 1; - if (serial != NULL) { - dinfo->serial = g_strdup(serial); - } + dinfo->bdrv->detect_zeroes = detect_zeroes; QTAILQ_INSERT_TAIL(&drives, dinfo, next); bdrv_set_on_error(dinfo->bdrv, on_read_error, on_write_error); @@ -523,12 +539,14 @@ static DriveInfo *blockdev_init(const char *file, QDict *bs_opts, err: bdrv_unref(dinfo->bdrv); - g_free(dinfo->id); QTAILQ_REMOVE(&drives, dinfo, next); +bdrv_new_err: + g_free(dinfo->id); g_free(dinfo); early_err: - QDECREF(bs_opts); qemu_opts_del(opts); +err_no_opts: + QDECREF(bs_opts); return NULL; } @@ -592,6 +610,10 @@ QemuOptsList qemu_legacy_drive_opts = { .type = QEMU_OPT_STRING, .help = "pci address (virtio only)", },{ + .name = "serial", + .type = QEMU_OPT_STRING, + .help = "disk serial number", + },{ .name = "file", .type = QEMU_OPT_STRING, .help = "file name", @@ -620,7 +642,7 @@ QemuOptsList qemu_legacy_drive_opts = { }, }; -DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type) +DriveInfo *drive_new(QemuOpts *all_opts, BlockInterfaceType block_default_type) { const char *value; DriveInfo *dinfo = NULL; @@ -634,6 +656,7 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type) const char *werror, *rerror; bool read_only = false; bool copy_on_read; + const char *serial; const char *filename; Error *local_err = NULL; @@ -692,7 +715,7 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type) &error_abort); qemu_opts_absorb_qdict(legacy_opts, bs_opts, &local_err); if (local_err) { - qerror_report_err(local_err); + error_report("%s", error_get_pretty(local_err)); error_free(local_err); goto fail; } @@ -837,6 +860,9 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type) goto fail; } + /* Serial number */ + serial = qemu_opt_get(legacy_opts, "serial"); + /* no id supplied -> create one */ if (qemu_opts_id(all_opts) == NULL) { char *new_id; @@ -902,9 +928,10 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type) /* Actual block device init: Functionality shared with blockdev-add */ dinfo = blockdev_init(filename, bs_opts, &local_err); + bs_opts = NULL; if (dinfo == NULL) { if (local_err) { - qerror_report_err(local_err); + error_report("%s", error_get_pretty(local_err)); error_free(local_err); } goto fail; @@ -926,6 +953,8 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type) dinfo->unit = unit_id; dinfo->devaddr = devaddr; + dinfo->serial = g_strdup(serial); + switch(type) { case IF_IDE: case IF_SCSI: @@ -939,6 +968,7 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type) fail: qemu_opts_del(legacy_opts); + QDECREF(bs_opts); return dinfo; } @@ -1116,6 +1146,7 @@ typedef struct InternalSnapshotState { static void internal_snapshot_prepare(BlkTransactionState *common, Error **errp) { + Error *local_err = NULL; const char *device; const char *name; BlockDriverState *bs; @@ -1164,8 +1195,10 @@ static void internal_snapshot_prepare(BlkTransactionState *common, } /* check whether a snapshot with name exist */ - ret = bdrv_snapshot_find_by_id_and_name(bs, NULL, name, &old_sn, errp); - if (error_is_set(errp)) { + ret = bdrv_snapshot_find_by_id_and_name(bs, NULL, name, &old_sn, + &local_err); + if (local_err) { + error_propagate(errp, local_err); return; } else if (ret) { error_setg(errp, @@ -1293,8 +1326,8 @@ static void external_snapshot_prepare(BlkTransactionState *common, return; } - if (bdrv_in_use(state->old_bs)) { - error_set(errp, QERR_DEVICE_IN_USE, device); + if (bdrv_op_is_blocked(state->old_bs, + BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, errp)) { return; } @@ -1516,19 +1549,20 @@ exit: static void eject_device(BlockDriverState *bs, int force, Error **errp) { - if (bdrv_in_use(bs)) { - error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bs)); + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_EJECT, errp)) { return; } if (!bdrv_dev_has_removable_media(bs)) { - error_set(errp, QERR_DEVICE_NOT_REMOVABLE, bdrv_get_device_name(bs)); + error_setg(errp, "Device '%s' is not removable", + bdrv_get_device_name(bs)); return; } if (bdrv_dev_is_medium_locked(bs) && !bdrv_dev_is_tray_open(bs)) { bdrv_dev_eject_request(bs, force); if (!force) { - error_set(errp, QERR_DEVICE_LOCKED, bdrv_get_device_name(bs)); + error_setg(errp, "Device '%s' is locked", + bdrv_get_device_name(bs)); return; } } @@ -1659,6 +1693,7 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd, { ThrottleConfig cfg; BlockDriverState *bs; + AioContext *aio_context; bs = bdrv_find(device); if (!bs) { @@ -1702,6 +1737,9 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd, return; } + aio_context = bdrv_get_aio_context(bs); + aio_context_acquire(aio_context); + if (!bs->io_limits_enabled && throttle_enabled(&cfg)) { bdrv_io_limits_enable(bs); } else if (bs->io_limits_enabled && !throttle_enabled(&cfg)) { @@ -1711,20 +1749,24 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd, if (bs->io_limits_enabled) { bdrv_set_io_limits(bs, &cfg); } + + aio_context_release(aio_context); } int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data) { const char *id = qdict_get_str(qdict, "id"); BlockDriverState *bs; + Error *local_err = NULL; bs = bdrv_find(id); if (!bs) { - qerror_report(QERR_DEVICE_NOT_FOUND, id); + error_report("Device '%s' not found", id); return -1; } - if (bdrv_in_use(bs)) { - qerror_report(QERR_DEVICE_IN_USE, id); + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_DRIVE_DEL, &local_err)) { + error_report("%s", error_get_pretty(local_err)); + error_free(local_err); return -1; } @@ -1745,7 +1787,7 @@ int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data) bdrv_set_on_error(bs, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT); } else { - drive_uninit(drive_get_by_blockdev(bs)); + drive_del(drive_get_by_blockdev(bs)); } return 0; @@ -1777,6 +1819,11 @@ void qmp_block_resize(bool has_device, const char *device, return; } + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_RESIZE, NULL)) { + error_set(errp, QERR_DEVICE_IN_USE, device); + return; + } + /* complete all in-flight operations before resizing the device */ bdrv_drain_all(); @@ -1805,35 +1852,36 @@ void qmp_block_resize(bool has_device, const char *device, static void block_job_cb(void *opaque, int ret) { BlockDriverState *bs = opaque; - QObject *obj; + const char *msg = NULL; trace_block_job_cb(bs, bs->job, ret); assert(bs->job); - obj = qobject_from_block_job(bs->job); + if (ret < 0) { - QDict *dict = qobject_to_qdict(obj); - qdict_put(dict, "error", qstring_from_str(strerror(-ret))); + msg = strerror(-ret); } if (block_job_is_cancelled(bs->job)) { - monitor_protocol_event(QEVENT_BLOCK_JOB_CANCELLED, obj); + block_job_event_cancelled(bs->job); } else { - monitor_protocol_event(QEVENT_BLOCK_JOB_COMPLETED, obj); + block_job_event_completed(bs->job, msg); } - qobject_decref(obj); bdrv_put_ref_bh_schedule(bs); } -void qmp_block_stream(const char *device, bool has_base, - const char *base, bool has_speed, int64_t speed, +void qmp_block_stream(const char *device, + bool has_base, const char *base, + bool has_backing_file, const char *backing_file, + bool has_speed, int64_t speed, bool has_on_error, BlockdevOnError on_error, Error **errp) { BlockDriverState *bs; BlockDriverState *base_bs = NULL; Error *local_err = NULL; + const char *base_name = NULL; if (!has_on_error) { on_error = BLOCKDEV_ON_ERROR_REPORT; @@ -1845,15 +1893,31 @@ void qmp_block_stream(const char *device, bool has_base, return; } - if (base) { + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_STREAM, errp)) { + return; + } + + if (has_base) { base_bs = bdrv_find_backing_image(bs, base); if (base_bs == NULL) { error_set(errp, QERR_BASE_NOT_FOUND, base); return; } + base_name = base; } - stream_start(bs, base_bs, base, has_speed ? speed : 0, + /* if we are streaming the entire chain, the result will have no backing + * file, and specifying one is therefore an error */ + if (base_bs == NULL && has_backing_file) { + error_setg(errp, "backing file specified, but streaming the " + "entire chain"); + return; + } + + /* backing_file string overrides base bs filename */ + base_name = has_backing_file ? backing_file : base_name; + + stream_start(bs, base_bs, base_name, has_speed ? speed : 0, on_error, block_job_cb, bs, &local_err); if (local_err) { error_propagate(errp, local_err); @@ -1864,7 +1928,9 @@ void qmp_block_stream(const char *device, bool has_base, } void qmp_block_commit(const char *device, - bool has_base, const char *base, const char *top, + bool has_base, const char *base, + bool has_top, const char *top, + bool has_backing_file, const char *backing_file, bool has_speed, int64_t speed, Error **errp) { @@ -1883,16 +1949,25 @@ void qmp_block_commit(const char *device, /* drain all i/o before commits */ bdrv_drain_all(); + /* Important Note: + * libvirt relies on the DeviceNotFound error class in order to probe for + * live commit feature versions; for this to work, we must make sure to + * perform the device lookup before any generic errors that may occur in a + * scenario in which all optional arguments are omitted. */ bs = bdrv_find(device); if (!bs) { error_set(errp, QERR_DEVICE_NOT_FOUND, device); return; } + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT, errp)) { + return; + } + /* default top_bs is the active layer */ top_bs = bs; - if (top) { + if (has_top && top) { if (strcmp(bs->filename, top) != 0) { top_bs = bdrv_find_backing_image(bs, top); } @@ -1914,12 +1989,23 @@ void qmp_block_commit(const char *device, return; } + /* Do not allow attempts to commit an image into itself */ + if (top_bs == base_bs) { + error_setg(errp, "cannot commit an image into itself"); + return; + } + if (top_bs == bs) { + if (has_backing_file) { + error_setg(errp, "'backing-file' specified," + " but 'top' is the active layer"); + return; + } commit_active_start(bs, base_bs, speed, on_error, block_job_cb, bs, &local_err); } else { commit_start(bs, base_bs, top_bs, speed, on_error, block_job_cb, bs, - &local_err); + has_backing_file ? backing_file : NULL, &local_err); } if (local_err != NULL) { error_propagate(errp, local_err); @@ -1980,8 +2066,7 @@ void qmp_drive_backup(const char *device, const char *target, } } - if (bdrv_in_use(bs)) { - error_set(errp, QERR_DEVICE_IN_USE, device); + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) { return; } @@ -2047,6 +2132,8 @@ BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp) void qmp_drive_mirror(const char *device, const char *target, bool has_format, const char *format, + bool has_node_name, const char *node_name, + bool has_replaces, const char *replaces, enum MirrorSyncMode sync, bool has_mode, enum NewImageMode mode, bool has_speed, int64_t speed, @@ -2060,6 +2147,7 @@ void qmp_drive_mirror(const char *device, const char *target, BlockDriverState *source, *target_bs; BlockDriver *drv = NULL; Error *local_err = NULL; + QDict *options = NULL; int flags; int64_t size; int ret; @@ -2114,8 +2202,7 @@ void qmp_drive_mirror(const char *device, const char *target, } } - if (bdrv_in_use(bs)) { - error_set(errp, QERR_DEVICE_IN_USE, device); + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_MIRROR, errp)) { return; } @@ -2134,6 +2221,29 @@ void qmp_drive_mirror(const char *device, const char *target, return; } + if (has_replaces) { + BlockDriverState *to_replace_bs; + + if (!has_node_name) { + error_setg(errp, "a node-name must be provided when replacing a" + " named node of the graph"); + return; + } + + to_replace_bs = check_to_replace_node(replaces, &local_err); + + if (!to_replace_bs) { + error_propagate(errp, local_err); + return; + } + + if (size != bdrv_getlength(to_replace_bs)) { + error_setg(errp, "cannot replace image with a mirror image of " + "different size"); + return; + } + } + if ((sync == MIRROR_SYNC_MODE_FULL || !source) && mode != NEW_IMAGE_MODE_EXISTING) { @@ -2162,18 +2272,28 @@ void qmp_drive_mirror(const char *device, const char *target, return; } + if (has_node_name) { + options = qdict_new(); + qdict_put(options, "node-name", qstring_from_str(node_name)); + } + /* Mirroring takes care of copy-on-write using the source's backing * file. */ target_bs = NULL; - ret = bdrv_open(&target_bs, target, NULL, NULL, flags | BDRV_O_NO_BACKING, - drv, &local_err); + ret = bdrv_open(&target_bs, target, NULL, options, + flags | BDRV_O_NO_BACKING, drv, &local_err); if (ret < 0) { error_propagate(errp, local_err); return; } - mirror_start(bs, target_bs, speed, granularity, buf_size, sync, + /* pass the node name to replace to mirror start since it's loose coupling + * and will allow to check whether the node still exist at mirror completion + */ + mirror_start(bs, target_bs, + has_replaces ? replaces : NULL, + speed, granularity, buf_size, sync, on_source_error, on_target_error, block_job_cb, bs, &local_err); if (local_err != NULL) { @@ -2220,7 +2340,8 @@ void qmp_block_job_cancel(const char *device, return; } if (job->paused && !force) { - error_set(errp, QERR_BLOCK_JOB_PAUSED, device); + error_setg(errp, "The block job for device '%s' is currently paused", + device); return; } @@ -2267,6 +2388,85 @@ void qmp_block_job_complete(const char *device, Error **errp) block_job_complete(job, errp); } +void qmp_change_backing_file(const char *device, + const char *image_node_name, + const char *backing_file, + Error **errp) +{ + BlockDriverState *bs = NULL; + BlockDriverState *image_bs = NULL; + Error *local_err = NULL; + bool ro; + int open_flags; + int ret; + + /* find the top layer BDS of the chain */ + bs = bdrv_find(device); + if (!bs) { + error_set(errp, QERR_DEVICE_NOT_FOUND, device); + return; + } + + image_bs = bdrv_lookup_bs(NULL, image_node_name, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + if (!image_bs) { + error_setg(errp, "image file not found"); + return; + } + + if (bdrv_find_base(image_bs) == image_bs) { + error_setg(errp, "not allowing backing file change on an image " + "without a backing file"); + return; + } + + /* even though we are not necessarily operating on bs, we need it to + * determine if block ops are currently prohibited on the chain */ + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_CHANGE, errp)) { + return; + } + + /* final sanity check */ + if (!bdrv_chain_contains(bs, image_bs)) { + error_setg(errp, "'%s' and image file are not in the same chain", + device); + return; + } + + /* if not r/w, reopen to make r/w */ + open_flags = image_bs->open_flags; + ro = bdrv_is_read_only(image_bs); + + if (ro) { + bdrv_reopen(image_bs, open_flags | BDRV_O_RDWR, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + } + + ret = bdrv_change_backing_file(image_bs, backing_file, + image_bs->drv ? image_bs->drv->format_name : ""); + + if (ret < 0) { + error_setg_errno(errp, -ret, "Could not change backing file to '%s'", + backing_file); + /* don't exit here, so we can try to restore open flags if + * appropriate */ + } + + if (ro) { + bdrv_reopen(image_bs, open_flags, &local_err); + if (local_err) { + error_propagate(errp, local_err); /* will preserve prior errp */ + } + } +} + void qmp_blockdev_add(BlockdevOptions *options, Error **errp) { QmpOutputVisitor *ov = qmp_output_visitor_new(); @@ -2281,9 +2481,9 @@ void qmp_blockdev_add(BlockdevOptions *options, Error **errp) goto fail; } - /* TODO Sort it out in raw-posix and drive_init: Reject aio=native with + /* TODO Sort it out in raw-posix and drive_new(): Reject aio=native with * cache.direct=false instead of silently switching to aio=threads, except - * if called from drive_init. + * when called from drive_new(). * * For now, simply forbidding the combination for all drivers will do. */ if (options->has_aio && options->aio == BLOCKDEV_AIO_OPTIONS_NATIVE) { @@ -2315,7 +2515,7 @@ void qmp_blockdev_add(BlockdevOptions *options, Error **errp) } if (bdrv_key_required(dinfo->bdrv)) { - drive_uninit(dinfo); + drive_del(dinfo); error_setg(errp, "blockdev-add doesn't support encrypted devices"); goto fail; } @@ -2379,10 +2579,6 @@ QemuOptsList qemu_common_drive_opts = { .type = QEMU_OPT_STRING, .help = "disk format (raw, qcow2, ...)", },{ - .name = "serial", - .type = QEMU_OPT_STRING, - .help = "disk serial number", - },{ .name = "rerror", .type = QEMU_OPT_STRING, .help = "read error action", @@ -2450,6 +2646,10 @@ QemuOptsList qemu_common_drive_opts = { .name = "copy-on-read", .type = QEMU_OPT_BOOL, .help = "copy read data from backing file into image file", + },{ + .name = "detect-zeroes", + .type = QEMU_OPT_STRING, + .help = "try to optimize zero writes (off, on, unmap)", }, { /* end of list */ } }, |