diff --git a/QMP/qmp-events.txt b/QMP/qmp-events.txt index af586ec855..0cd2275c3b 100644 --- a/QMP/qmp-events.txt +++ b/QMP/qmp-events.txt @@ -264,3 +264,32 @@ Example: Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is followed respectively by the RESET, SHUTDOWN, or STOP events. + + +BLOCK_JOB_COMPLETED +------------------- + +Emitted when a block job has completed. + +Data: + +- "type": Job type ("stream" for image streaming, json-string) +- "device": Device name (json-string) +- "len": Maximum progress value (json-int) +- "offset": Current progress value (json-int) + On success this is equal to len. + On failure this is less than len. +- "speed": Rate limit, bytes per second (json-int) +- "error": Error message (json-string, optional) + Only present on failure. This field contains a human-readable + error message. There are no semantics other than that streaming + has failed and clients should not try to interpret the error + string. + +Example: + +{ "event": "BLOCK_JOB_COMPLETED", + "data": { "type": "stream", "device": "virtio-disk0", + "len": 10737418240, "offset": 10737418240, + "speed": 0 }, + "timestamp": { "seconds": 1267061043, "microseconds": 959568 } } diff --git a/blockdev.c b/blockdev.c index 0499ee6aea..06f179ef87 100644 --- a/blockdev.c +++ b/blockdev.c @@ -13,9 +13,11 @@ #include "qerror.h" #include "qemu-option.h" #include "qemu-config.h" +#include "qemu-objects.h" #include "sysemu.h" #include "block_int.h" #include "qmp-commands.h" +#include "trace.h" static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives); @@ -897,3 +899,68 @@ void qmp_block_resize(const char *device, int64_t size, Error **errp) break; } } + +static QObject *qobject_from_block_job(BlockJob *job) +{ + return qobject_from_jsonf("{ 'type': %s," + "'device': %s," + "'len': %" PRId64 "," + "'offset': %" PRId64 "," + "'speed': %" PRId64 " }", + job->job_type->job_type, + bdrv_get_device_name(job->bs), + job->len, + job->offset, + job->speed); +} + +static void block_stream_cb(void *opaque, int ret) +{ + BlockDriverState *bs = opaque; + QObject *obj; + + trace_block_stream_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))); + } + + monitor_protocol_event(QEVENT_BLOCK_JOB_COMPLETED, obj); + qobject_decref(obj); +} + +void qmp_block_stream(const char *device, bool has_base, + const char *base, Error **errp) +{ + BlockDriverState *bs; + int ret; + + bs = bdrv_find(device); + if (!bs) { + error_set(errp, QERR_DEVICE_NOT_FOUND, device); + return; + } + + /* Base device not supported */ + if (base) { + error_set(errp, QERR_NOT_SUPPORTED); + return; + } + + ret = stream_start(bs, NULL, block_stream_cb, bs); + if (ret < 0) { + switch (ret) { + case -EBUSY: + error_set(errp, QERR_DEVICE_IN_USE, device); + return; + default: + error_set(errp, QERR_NOT_SUPPORTED); + return; + } + } + + trace_qmp_block_stream(bs, bs->job); +} diff --git a/hmp-commands.hx b/hmp-commands.hx index e6506fc9d3..5f09594756 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -69,6 +69,19 @@ but should be used with extreme caution. Note that this command only resizes image files, it can not resize block devices like LVM volumes. ETEXI + { + .name = "block_stream", + .args_type = "device:B,base:s?", + .params = "device [base]", + .help = "copy data from a backing file into a block device", + .mhandler.cmd = hmp_block_stream, + }, + +STEXI +@item block_stream +@findex block_stream +Copy data from a backing file into a block device. +ETEXI { .name = "eject", diff --git a/hmp.c b/hmp.c index 4664dbe8e4..cde8d7a5cb 100644 --- a/hmp.c +++ b/hmp.c @@ -783,3 +783,14 @@ void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict) qdict_get_int(qdict, "iops_wr"), &err); hmp_handle_error(mon, &err); } + +void hmp_block_stream(Monitor *mon, const QDict *qdict) +{ + Error *error = NULL; + const char *device = qdict_get_str(qdict, "device"); + const char *base = qdict_get_try_str(qdict, "base"); + + qmp_block_stream(device, base != NULL, base, &error); + + hmp_handle_error(mon, &error); +} diff --git a/hmp.h b/hmp.h index aab0b1f508..9234f28db6 100644 --- a/hmp.h +++ b/hmp.h @@ -54,5 +54,6 @@ void hmp_expire_password(Monitor *mon, const QDict *qdict); void hmp_eject(Monitor *mon, const QDict *qdict); void hmp_change(Monitor *mon, const QDict *qdict); void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict); +void hmp_block_stream(Monitor *mon, const QDict *qdict); #endif diff --git a/monitor.c b/monitor.c index 187083c450..3d8cbfbee3 100644 --- a/monitor.c +++ b/monitor.c @@ -479,6 +479,9 @@ void monitor_protocol_event(MonitorEvent event, QObject *data) case QEVENT_SPICE_DISCONNECTED: event_name = "SPICE_DISCONNECTED"; break; + case QEVENT_BLOCK_JOB_COMPLETED: + event_name = "BLOCK_JOB_COMPLETED"; + break; default: abort(); break; diff --git a/monitor.h b/monitor.h index 887c472a92..34d00d107e 100644 --- a/monitor.h +++ b/monitor.h @@ -36,6 +36,7 @@ typedef enum MonitorEvent { QEVENT_SPICE_CONNECTED, QEVENT_SPICE_INITIALIZED, QEVENT_SPICE_DISCONNECTED, + QEVENT_BLOCK_JOB_COMPLETED, QEVENT_MAX, } MonitorEvent; diff --git a/qapi-schema.json b/qapi-schema.json index 735eb352b5..9a1d9a936b 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1434,3 +1434,34 @@ { 'command': 'block_set_io_throttle', 'data': { 'device': 'str', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int' } } + +# @block_stream: +# +# Copy data from a backing file into a block device. +# +# The block streaming operation is performed in the background until the entire +# backing file has been copied. This command returns immediately once streaming +# has started. The status of ongoing block streaming operations can be checked +# with query-block-jobs. The operation can be stopped before it has completed +# using the block_job_cancel command. +# +# If a base file is specified then sectors are not copied from that base file and +# its backing chain. When streaming completes the image file will have the base +# file as its backing file. This can be used to stream a subset of the backing +# file chain instead of flattening the entire image. +# +# On successful completion the image file is updated to drop the backing file +# and the BLOCK_JOB_COMPLETED event is emitted. +# +# @device: the device name +# +# @base: #optional the common backing file name +# +# Returns: Nothing on success +# If streaming is already active on this device, DeviceInUse +# If @device does not exist, DeviceNotFound +# If image streaming is not supported by this device, NotSupported +# +# Since: 1.1 +## +{ 'command': 'block_stream', 'data': { 'device': 'str', '*base': 'str' } } diff --git a/qerror.c b/qerror.c index 3d95383940..2c60e10631 100644 --- a/qerror.c +++ b/qerror.c @@ -196,6 +196,10 @@ static const QErrorStringTable qerror_table[] = { .error_fmt = QERR_NO_BUS_FOR_DEVICE, .desc = "No '%(bus)' bus found for device '%(device)'", }, + { + .error_fmt = QERR_NOT_SUPPORTED, + .desc = "Not supported", + }, { .error_fmt = QERR_OPEN_FILE_FAILED, .desc = "Could not open '%(filename)'", diff --git a/qerror.h b/qerror.h index 89160dd78e..b530bc8806 100644 --- a/qerror.h +++ b/qerror.h @@ -168,6 +168,9 @@ QError *qobject_to_qerror(const QObject *obj); #define QERR_NO_BUS_FOR_DEVICE \ "{ 'class': 'NoBusForDevice', 'data': { 'device': %s, 'bus': %s } }" +#define QERR_NOT_SUPPORTED \ + "{ 'class': 'NotSupported', 'data': {} }" + #define QERR_OPEN_FILE_FAILED \ "{ 'class': 'OpenFileFailed', 'data': { 'filename': %s } }" diff --git a/qmp-commands.hx b/qmp-commands.hx index 799e655988..0dfc80e26f 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -648,6 +648,12 @@ Example: EQMP + { + .name = "block_stream", + .args_type = "device:B,base:s?", + .mhandler.cmd_new = qmp_marshal_input_block_stream, + }, + { .name = "blockdev-snapshot-sync", .args_type = "device:B,snapshot-file:s,format:s?", diff --git a/trace-events b/trace-events index f6a3cd1aaf..5edba21a89 100644 --- a/trace-events +++ b/trace-events @@ -74,6 +74,10 @@ bdrv_co_do_copy_on_readv(void *bs, int64_t sector_num, int nb_sectors, int64_t c stream_one_iteration(void *s, int64_t sector_num, int nb_sectors, int is_allocated) "s %p sector_num %"PRId64" nb_sectors %d is_allocated %d" stream_start(void *bs, void *base, void *s, void *co, void *opaque) "bs %p base %p s %p co %p opaque %p" +# blockdev.c +block_stream_cb(void *bs, void *job, int ret) "bs %p job %p ret %d" +qmp_block_stream(void *bs, void *job) "bs %p job %p" + # hw/virtio-blk.c virtio_blk_req_complete(void *req, int status) "req %p status %d" virtio_blk_rw_complete(void *req, int ret) "req %p ret %d"