@@ -13,8 +13,10 @@
#include "qerror.h"
#include "qemu-option.h"
#include "qemu-config.h"
+#include "qemu-objects.h"
#include "sysemu.h"
#include "block_int.h"
+#include "trace.h"
static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
@@ -782,3 +784,70 @@ int do_block_resize(Monitor *mon, const QDict *qdict, QObject **ret_data)
return 0;
}
+
+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);
+}
+
+int do_block_stream(Monitor *mon, const QDict *params, QObject **ret_data)
+{
+ const char *device = qdict_get_str(params, "device");
+ const char *base = qdict_get_try_str(params, "base");
+ BlockDriverState *bs;
+ int ret;
+
+ bs = bdrv_find(device);
+ if (!bs) {
+ qerror_report(QERR_DEVICE_NOT_FOUND, device);
+ return -1;
+ }
+
+ /* Base device not supported */
+ if (base) {
+ qerror_report(QERR_NOT_SUPPORTED);
+ return -1;
+ }
+
+ ret = stream_start(bs, NULL, block_stream_cb, bs);
+ if (ret < 0) {
+ switch (ret) {
+ case -EBUSY:
+ qerror_report(QERR_DEVICE_IN_USE, device);
+ return -1;
+ default:
+ qerror_report(QERR_NOT_SUPPORTED);
+ return -1;
+ }
+ }
+
+ trace_do_block_stream(bs, bs->job);
+ return 0;
+}
@@ -65,5 +65,6 @@ int do_change_block(Monitor *mon, const char *device,
int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data);
int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data);
int do_block_resize(Monitor *mon, const QDict *qdict, QObject **ret_data);
+int do_block_stream(Monitor *mon, const QDict *qdict, QObject **ret_data);
#endif
@@ -70,6 +70,20 @@ 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",
+ .user_print = monitor_user_noop,
+ .mhandler.cmd_new = do_block_stream,
+ },
+
+STEXI
+@item block_stream
+@findex block_stream
+Copy data from a backing file into a block device.
+ETEXI
{
.name = "eject",
@@ -482,6 +482,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;
@@ -35,6 +35,7 @@ typedef enum MonitorEvent {
QEVENT_SPICE_CONNECTED,
QEVENT_SPICE_INITIALIZED,
QEVENT_SPICE_DISCONNECTED,
+ QEVENT_BLOCK_JOB_COMPLETED,
QEVENT_MAX,
} MonitorEvent;
@@ -162,6 +162,10 @@ static const QErrorStringTable qerror_table[] = {
.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)'",
},
@@ -141,6 +141,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 } }"
@@ -685,6 +685,71 @@ Example:
EQMP
{
+ .name = "block_stream",
+ .args_type = "device:B,base:s?",
+ .params = "device [base]",
+ .user_print = monitor_user_noop,
+ .mhandler.cmd_new = do_block_stream,
+ },
+
+SQMP
+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.
+
+Arguments:
+
+- device: device name (json-string)
+- base: common backing file (json-string, optional)
+
+Errors:
+
+DeviceInUse: streaming is already active on this device
+DeviceNotFound: device name is invalid
+NotSupported: image streaming is not supported by this device
+
+Events:
+
+On completion the BLOCK_JOB_COMPLETED event is raised with the following
+fields:
+
+- 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)
+- speed: rate limit, bytes per second (json-int)
+- error: error message (json-string, only on error)
+
+The completion event is raised both on success and on failure. On
+success offset is equal to len. On failure offset and len can be
+used to indicate at which point the operation failed.
+
+On failure the error 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.
+
+Examples:
+
+-> { "execute": "block_stream", "arguments": { "device": "virtio0" } }
+<- { "return": {} }
+
+EQMP
+
+ {
.name = "blockdev-snapshot-sync",
.args_type = "device:B,snapshot-file:s?,format:s?",
.params = "device [new-image-file] [format]",
@@ -75,6 +75,10 @@ bdrv_co_copy_on_readv(void *bs, int64_t sector_num, int nb_sectors, int64_t clus
stream_one_iteration(void *s, int64_t sector_num, int max_sectors) "s %p sector_num %"PRId64" max_sectors %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"
+do_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"
Add the block_stream command, which starts copy backing file contents into the image file. Later patches add control over the background copy speed, cancelation, and querying running streaming operations. Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> --- blockdev.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ blockdev.h | 1 + hmp-commands.hx | 14 +++++++++++ monitor.c | 3 ++ monitor.h | 1 + qerror.c | 4 +++ qerror.h | 3 ++ qmp-commands.hx | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ trace-events | 4 +++ 9 files changed, 164 insertions(+), 0 deletions(-)