All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches
@ 2012-06-15 15:05 Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands Paolo Bonzini
                   ` (35 more replies)
  0 siblings, 36 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Hi all, this is the current state of my block job patches for 1.2.
Everything is there except multiple in-flight operations in the mirroring
job.  Mirroring testcases are especially missing; mirroring error
reporting and QMP commands are tested very lightly, which is expected
to change when testcases are added.

The first 17 patches are ready for inclusion, but they depend on three
patches currently in Luiz's queue:
- kvm: add missing include files
- net: add missing include file
- qmp: do not include monitor.h from qapi-types-core.h

They also depend on the blkdebug changes that I have already posted.
Because of the web of dependencies I'm conservatively tagging everything
as RFC.  Reviewed-bys are nevertheless welcome, especially on those
patches.  If you want to tinker with this, everything is available at
git://github.com/bonzini/qemu.git in branch blkmirror-job-1.2.

The series includes a generalization of the dirty bitmap API to use
a trie-like hierarchical bitmap and to allow arbitrary granularity.  This
is accessible through an extra argument of the QMP drive-mirror command.

Paolo Bonzini (36):
  qapi: generalize documentation of streaming commands
  qerror/block: introduce QERR_BLOCK_JOB_NOT_ACTIVE
  block: move job APIs to separate files
  block: add block_job_query
  block: add support for job pause/resume
  qmp: add block-job-pause and block-job-resume
  qemu-iotests: add test for pausing a streaming operation
  block: rename block_job_complete to block_job_completed
  block: rename BlockErrorAction, BlockQMPEventAction
  block: move BlockdevOnError declaration to QAPI
  block: reorganize io error code
  block: sort BlockDeviceIoStatus errors by severity
  block: introduce block job error
  stream: add on_error argument
  qemu-iotests: add tests for streaming error handling
  block: add bdrv_query_info
  block: add bdrv_query_stats
  block: make device optional in BlockInfo
  block: add target info to QMP query-blockjobs command
  block: forward bdrv_iostatus_reset to block job
  block: introduce new dirty bitmap functionality
  block: add mirror job
  qmp: add drive-mirror command
  mirror: support querying target file
  mirror: add support for on_source_error/on_target_error
  block: live snapshot documentation tweaks
  block: add bdrv_ensure_backing_file
  block: add block-job-complete
  mirror: implement completion
  add hierarchical bitmap data type and test cases
  block: implement dirty bitmap using HBitmap
  block: make round_to_clusters public
  mirror: perform COW if the cluster size is bigger than the granularity
  block: return count of dirty sectors, not chunks
  block: allow customizing the granularity of the dirty bitmap
  mirror: allow customizing the granularity

 Makefile.objs                 |    5 +-
 QMP/qmp-events.txt            |   27 +++
 block-migration.c             |    8 +-
 block.c                       |  486 ++++++++++++++++-------------------------
 block.h                       |   37 ++--
 block/Makefile.objs           |    3 +-
 block/mirror.c                |  347 +++++++++++++++++++++++++++++
 block/stream.c                |   33 ++-
 block_int.h                   |  191 +++-------------
 blockdev.c                    |  224 ++++++++++++++++---
 blockjob.c                    |  269 +++++++++++++++++++++++
 blockjob.h                    |  272 +++++++++++++++++++++++
 hbitmap.c                     |  371 +++++++++++++++++++++++++++++++
 hbitmap.h                     |   51 +++++
 hmp-commands.hx               |   73 ++++++-
 hmp.c                         |   65 +++++-
 hmp.h                         |    4 +
 hw/fdc.c                      |    4 +-
 hw/ide/core.c                 |   20 +-
 hw/scsi-disk.c                |   23 +-
 hw/scsi-generic.c             |    4 +-
 hw/virtio-blk.c               |   19 +-
 monitor.c                     |    1 +
 monitor.h                     |    1 +
 qapi-schema.json              |  214 ++++++++++++++++--
 qemu-tool.c                   |    6 +
 qerror.c                      |   12 +
 qerror.h                      |    9 +
 qmp-commands.hx               |   68 +++++-
 tests/Makefile                |    2 +
 tests/qemu-iotests/030        |  166 +++++++++++++-
 tests/qemu-iotests/iotests.py |    7 +
 tests/test-hbitmap.c          |  369 +++++++++++++++++++++++++++++++
 trace-events                  |   14 +-
 34 files changed, 2828 insertions(+), 577 deletions(-)
 create mode 100644 block/mirror.c
 create mode 100644 blockjob.c
 create mode 100644 blockjob.h
 create mode 100644 hbitmap.c
 create mode 100644 hbitmap.h
 create mode 100644 tests/test-hbitmap.c

-- 
1.7.10.2

^ permalink raw reply	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 16:45   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 02/36] qerror/block: introduce QERR_BLOCK_JOB_NOT_ACTIVE Paolo Bonzini
                   ` (34 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Talk about background operations in general, rather than specifically
about streaming.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 hmp-commands.hx  |    2 +-
 qapi-schema.json |   17 ++++++++---------
 2 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/hmp-commands.hx b/hmp-commands.hx
index f5d9d91..26a4d8d 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -101,7 +101,7 @@ ETEXI
         .name       = "block_job_cancel",
         .args_type  = "device:B",
         .params     = "device",
-        .help       = "stop an active block streaming operation",
+        .help       = "stop an active background block operation",
         .mhandler.cmd = hmp_block_job_cancel,
     },
 
diff --git a/qapi-schema.json b/qapi-schema.json
index 3b6e346..eca0872 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1651,7 +1651,7 @@
 # Returns: Nothing on success
 #          If the job type does not support throttling, NotSupported
 #          If the speed value is invalid, InvalidParameter
-#          If streaming is not active on this device, DeviceNotActive
+#          If no background operation is active on this device, DeviceNotActive
 #
 # Since: 1.1
 ##
@@ -1661,9 +1661,9 @@
 ##
 # @block-job-cancel:
 #
-# Stop an active block streaming operation.
+# Stop an active background block operation.
 #
-# This command returns immediately after marking the active block streaming
+# This command returns immediately after marking the active background block
 # operation for cancellation.  It is an error to call this command if no
 # operation is in progress.
 #
@@ -1671,16 +1671,15 @@
 # BLOCK_JOB_CANCELLED event.  Before that happens the job is still visible when
 # enumerated using query-block-jobs.
 #
-# The image file retains its backing file unless the streaming operation happens
-# to complete just as it is being cancelled.
-#
-# A new block streaming operation can be started at a later time to finish
-# copying all data from the backing file.
+# For streaming, the image file retains its backing file unless the streaming
+# operation happens to complete just as it is being cancelled.  A new streaming
+# operation can be started at a later time to finish copying all data from the
+# backing file.
 #
 # @device: the device name
 #
 # Returns: Nothing on success
-#          If streaming is not active on this device, DeviceNotActive
+#          If no background operation is active on this device, DeviceNotActive
 #          If cancellation already in progress, DeviceInUse
 #
 # Since: 1.1
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 02/36] qerror/block: introduce QERR_BLOCK_JOB_NOT_ACTIVE
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 16:51   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 03/36] block: move job APIs to separate files Paolo Bonzini
                   ` (33 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

The DeviceNotActive error is not a particularly good match, add
a separate one.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockdev.c       |    4 ++--
 qapi-schema.json |    5 ++---
 qerror.c         |    4 ++++
 qerror.h         |    3 +++
 4 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index 9e0a72a..a3518f0 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1143,7 +1143,7 @@ void qmp_block_job_set_speed(const char *device, int64_t speed, Error **errp)
     BlockJob *job = find_block_job(device);
 
     if (!job) {
-        error_set(errp, QERR_DEVICE_NOT_ACTIVE, device);
+        error_set(errp, QERR_BLOCK_JOB_NOT_ACTIVE, device);
         return;
     }
 
@@ -1155,7 +1155,7 @@ void qmp_block_job_cancel(const char *device, Error **errp)
     BlockJob *job = find_block_job(device);
 
     if (!job) {
-        error_set(errp, QERR_DEVICE_NOT_ACTIVE, device);
+        error_set(errp, QERR_BLOCK_JOB_NOT_ACTIVE, device);
         return;
     }
 
diff --git a/qapi-schema.json b/qapi-schema.json
index eca0872..35c16f3 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1651,7 +1651,7 @@
 # Returns: Nothing on success
 #          If the job type does not support throttling, NotSupported
 #          If the speed value is invalid, InvalidParameter
-#          If no background operation is active on this device, DeviceNotActive
+#          If no background operation is active on this device, BlockJobNotActive
 #
 # Since: 1.1
 ##
@@ -1679,8 +1679,7 @@
 # @device: the device name
 #
 # Returns: Nothing on success
-#          If no background operation is active on this device, DeviceNotActive
-#          If cancellation already in progress, DeviceInUse
+#          If no background operation is active on this device, BlockJobNotActive
 #
 # Since: 1.1
 ##
diff --git a/qerror.c b/qerror.c
index 92c4eff..bc672a5 100644
--- a/qerror.c
+++ b/qerror.c
@@ -60,6 +60,10 @@ static const QErrorStringTable qerror_table[] = {
         .desc      = "Base '%(base)' not found",
     },
     {
+        .error_fmt = QERR_BLOCK_JOB_NOT_ACTIVE,
+        .desc      = "No active block job on device '%(name)'",
+    },
+    {
         .error_fmt = QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
         .desc      = "Block format '%(format)' used by device '%(name)' does not support feature '%(feature)'",
     },
diff --git a/qerror.h b/qerror.h
index b4c8758..7cf7d22 100644
--- a/qerror.h
+++ b/qerror.h
@@ -64,6 +64,9 @@ QError *qobject_to_qerror(const QObject *obj);
 #define QERR_BASE_NOT_FOUND \
     "{ 'class': 'BaseNotFound', 'data': { 'base': %s } }"
 
+#define QERR_BLOCK_JOB_NOT_ACTIVE \
+    "{ 'class': 'BlockJobNotActive', 'data': { 'name': %s } }"
+
 #define QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED \
     "{ 'class': 'BlockFormatFeatureNotSupported', 'data': { 'format': %s, 'name': %s, 'feature': %s } }"
 
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 03/36] block: move job APIs to separate files
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 02/36] qerror/block: introduce QERR_BLOCK_JOB_NOT_ACTIVE Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 04/36] block: add block_job_query Paolo Bonzini
                   ` (32 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 Makefile.objs       |    5 +-
 block.c             |  128 +----------------------------------
 block.h             |    2 +
 block/Makefile.objs |    3 +-
 block/stream.c      |    1 +
 block_int.h         |  153 ------------------------------------------
 blockdev.c          |    1 +
 blockjob.c          |  164 +++++++++++++++++++++++++++++++++++++++++++++
 blockjob.h          |  184 +++++++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 358 insertions(+), 283 deletions(-)
 create mode 100644 blockjob.c
 create mode 100644 blockjob.h

diff --git a/Makefile.objs b/Makefile.objs
index 74110dd..cef9933 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -41,7 +41,8 @@ coroutine-obj-$(CONFIG_WIN32) += coroutine-win32.o
 # block-obj-y is code used by both qemu system emulation and qemu-img
 
 block-obj-y = cutils.o cache-utils.o qemu-option.o module.o async.o
-block-obj-y += nbd.o block.o aio.o aes.o qemu-config.o qemu-progress.o qemu-sockets.o
+block-obj-y += nbd.o block.o blockjob.o aio.o aes.o qemu-config.o
+block-obj-y += qemu-progress.o qemu-sockets.o
 block-obj-y += $(coroutine-obj-y) $(qobject-obj-y) $(version-obj-y)
 block-obj-$(CONFIG_POSIX) += posix-aio-compat.o
 block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
@@ -58,7 +59,7 @@ endif
 # suppress *all* target specific code in case of system emulation, i.e. a
 # single QEMU executable should support all CPUs and machines.
 
-common-obj-y = $(block-obj-y) blockdev.o
+common-obj-y = $(block-obj-y) blockdev.o block/
 common-obj-y += net.o net/
 common-obj-y += qom/
 common-obj-y += readline.o console.o cursor.o
diff --git a/block.c b/block.c
index 29857db..c396d58 100644
--- a/block.c
+++ b/block.c
@@ -26,6 +26,7 @@
 #include "trace.h"
 #include "monitor.h"
 #include "block_int.h"
+#include "blockjob.h"
 #include "module.h"
 #include "qjson.h"
 #include "qemu-coroutine.h"
@@ -4276,130 +4277,3 @@ out:
 
     return ret;
 }
-
-void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
-                       int64_t speed, BlockDriverCompletionFunc *cb,
-                       void *opaque, Error **errp)
-{
-    BlockJob *job;
-
-    if (bs->job || bdrv_in_use(bs)) {
-        error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bs));
-        return NULL;
-    }
-    bdrv_set_in_use(bs, 1);
-
-    job = g_malloc0(job_type->instance_size);
-    job->job_type      = job_type;
-    job->bs            = bs;
-    job->cb            = cb;
-    job->opaque        = opaque;
-    job->busy          = true;
-    bs->job = job;
-
-    /* Only set speed when necessary to avoid NotSupported error */
-    if (speed != 0) {
-        Error *local_err = NULL;
-
-        block_job_set_speed(job, speed, &local_err);
-        if (error_is_set(&local_err)) {
-            bs->job = NULL;
-            g_free(job);
-            bdrv_set_in_use(bs, 0);
-            error_propagate(errp, local_err);
-            return NULL;
-        }
-    }
-    return job;
-}
-
-void block_job_complete(BlockJob *job, int ret)
-{
-    BlockDriverState *bs = job->bs;
-
-    assert(bs->job == job);
-    job->cb(job->opaque, ret);
-    bs->job = NULL;
-    g_free(job);
-    bdrv_set_in_use(bs, 0);
-}
-
-void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
-{
-    Error *local_err = NULL;
-
-    if (!job->job_type->set_speed) {
-        error_set(errp, QERR_NOT_SUPPORTED);
-        return;
-    }
-    job->job_type->set_speed(job, speed, &local_err);
-    if (error_is_set(&local_err)) {
-        error_propagate(errp, local_err);
-        return;
-    }
-
-    job->speed = speed;
-}
-
-void block_job_cancel(BlockJob *job)
-{
-    job->cancelled = true;
-    if (job->co && !job->busy) {
-        qemu_coroutine_enter(job->co, NULL);
-    }
-}
-
-bool block_job_is_cancelled(BlockJob *job)
-{
-    return job->cancelled;
-}
-
-struct BlockCancelData {
-    BlockJob *job;
-    BlockDriverCompletionFunc *cb;
-    void *opaque;
-    bool cancelled;
-    int ret;
-};
-
-static void block_job_cancel_cb(void *opaque, int ret)
-{
-    struct BlockCancelData *data = opaque;
-
-    data->cancelled = block_job_is_cancelled(data->job);
-    data->ret = ret;
-    data->cb(data->opaque, ret);
-}
-
-int block_job_cancel_sync(BlockJob *job)
-{
-    struct BlockCancelData data;
-    BlockDriverState *bs = job->bs;
-
-    assert(bs->job == job);
-
-    /* Set up our own callback to store the result and chain to
-     * the original callback.
-     */
-    data.job = job;
-    data.cb = job->cb;
-    data.opaque = job->opaque;
-    data.ret = -EINPROGRESS;
-    job->cb = block_job_cancel_cb;
-    job->opaque = &data;
-    block_job_cancel(job);
-    while (data.ret == -EINPROGRESS) {
-        qemu_aio_wait();
-    }
-    return (data.cancelled && data.ret == 0) ? -ECANCELED : data.ret;
-}
-
-void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns)
-{
-    /* Check cancellation *before* setting busy = false, too!  */
-    if (!block_job_is_cancelled(job)) {
-        job->busy = false;
-        co_sleep_ns(clock, ns);
-        job->busy = true;
-    }
-}
diff --git a/block.h b/block.h
index 3af93c6..298c616 100644
--- a/block.h
+++ b/block.h
@@ -6,9 +6,11 @@
 #include "qemu-option.h"
 #include "qemu-coroutine.h"
 #include "qobject.h"
+#include "qapi-types.h"
 
 /* block.c */
 typedef struct BlockDriver BlockDriver;
+typedef struct BlockJob BlockJob;
 
 typedef struct BlockDriverInfo {
     /* in bytes, 0 if irrelevant */
diff --git a/block/Makefile.objs b/block/Makefile.objs
index b5754d3..c45affc 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -3,9 +3,10 @@ block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-c
 block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
 block-obj-y += qed-check.o
 block-obj-y += parallels.o nbd.o blkdebug.o sheepdog.o blkverify.o
-block-obj-y += stream.o
 block-obj-$(CONFIG_WIN32) += raw-win32.o
 block-obj-$(CONFIG_POSIX) += raw-posix.o
 block-obj-$(CONFIG_LIBISCSI) += iscsi.o
 block-obj-$(CONFIG_CURL) += curl.o
 block-obj-$(CONFIG_RBD) += rbd.o
+
+common-obj-y += stream.o
diff --git a/block/stream.c b/block/stream.c
index 37c4652..5b801b4 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -13,6 +13,7 @@
 
 #include "trace.h"
 #include "block_int.h"
+#include "blockjob.h"
 #include "qemu/ratelimit.h"
 
 enum {
diff --git a/block_int.h b/block_int.h
index 1fb5352..4fef4f3 100644
--- a/block_int.h
+++ b/block_int.h
@@ -64,73 +64,6 @@ typedef struct BlockIOBaseValue {
     uint64_t ios[2];
 } BlockIOBaseValue;
 
-typedef struct BlockJob BlockJob;
-
-/**
- * BlockJobType:
- *
- * A class type for block job objects.
- */
-typedef struct BlockJobType {
-    /** Derived BlockJob struct size */
-    size_t instance_size;
-
-    /** String describing the operation, part of query-block-jobs QMP API */
-    const char *job_type;
-
-    /** Optional callback for job types that support setting a speed limit */
-    void (*set_speed)(BlockJob *job, int64_t speed, Error **errp);
-} BlockJobType;
-
-/**
- * BlockJob:
- *
- * Long-running operation on a BlockDriverState.
- */
-struct BlockJob {
-    /** The job type, including the job vtable.  */
-    const BlockJobType *job_type;
-
-    /** The block device on which the job is operating.  */
-    BlockDriverState *bs;
-
-    /**
-     * The coroutine that executes the job.  If not NULL, it is
-     * reentered when busy is false and the job is cancelled.
-     */
-    Coroutine *co;
-
-    /**
-     * Set to true if the job should cancel itself.  The flag must
-     * always be tested just before toggling the busy flag from false
-     * to true.  After a job has been cancelled, it should only yield
-     * if #qemu_aio_wait will ("sooner or later") reenter the coroutine.
-     */
-    bool cancelled;
-
-    /**
-     * Set to false by the job while it is in a quiescent state, where
-     * no I/O is pending and the job has yielded on any condition
-     * that is not detected by #qemu_aio_wait, such as a timer.
-     */
-    bool busy;
-
-    /** Offset that is published by the query-block-jobs QMP API */
-    int64_t offset;
-
-    /** Length that is published by the query-block-jobs QMP API */
-    int64_t len;
-
-    /** Speed that was set with @block_job_set_speed.  */
-    int64_t speed;
-
-    /** The completion function that will be called when the job completes.  */
-    BlockDriverCompletionFunc *cb;
-
-    /** The opaque value that is passed to the completion function.  */
-    void *opaque;
-};
-
 struct BlockDriver {
     const char *format_name;
     int instance_size;
@@ -346,92 +279,6 @@ int is_windows_drive(const char *filename);
 #endif
 
 /**
- * block_job_create:
- * @job_type: The class object for the newly-created job.
- * @bs: The block
- * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
- * @cb: Completion function for the job.
- * @opaque: Opaque pointer value passed to @cb.
- * @errp: Error object.
- *
- * Create a new long-running block device job and return it.  The job
- * will call @cb asynchronously when the job completes.  Note that
- * @bs may have been closed at the time the @cb it is called.  If
- * this is the case, the job may be reported as either cancelled or
- * completed.
- *
- * This function is not part of the public job interface; it should be
- * called from a wrapper that is specific to the job type.
- */
-void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
-                       int64_t speed, BlockDriverCompletionFunc *cb,
-                       void *opaque, Error **errp);
-
-/**
- * block_job_sleep_ns:
- * @job: The job that calls the function.
- * @clock: The clock to sleep on.
- * @ns: How many nanoseconds to stop for.
- *
- * Put the job to sleep (assuming that it wasn't canceled) for @ns
- * nanoseconds.  Canceling the job will interrupt the wait immediately.
- */
-void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns);
-
-/**
- * block_job_complete:
- * @job: The job being completed.
- * @ret: The status code.
- *
- * Call the completion function that was registered at creation time, and
- * free @job.
- */
-void block_job_complete(BlockJob *job, int ret);
-
-/**
- * block_job_set_speed:
- * @job: The job to set the speed for.
- * @speed: The new value
- * @errp: Error object.
- *
- * Set a rate-limiting parameter for the job; the actual meaning may
- * vary depending on the job type.
- */
-void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
-
-/**
- * block_job_cancel:
- * @job: The job to be canceled.
- *
- * Asynchronously cancel the specified job.
- */
-void block_job_cancel(BlockJob *job);
-
-/**
- * block_job_is_cancelled:
- * @job: The job being queried.
- *
- * Returns whether the job is scheduled for cancellation.
- */
-bool block_job_is_cancelled(BlockJob *job);
-
-/**
- * block_job_cancel:
- * @job: The job to be canceled.
- *
- * Asynchronously cancel the job and wait for it to reach a quiescent
- * state.  Note that the completion callback will still be called
- * asynchronously, hence it is *not* valid to call #bdrv_delete
- * immediately after #block_job_cancel_sync.  Users of block jobs
- * will usually protect the BlockDriverState objects with a reference
- * count, should this be a concern.
- *
- * Returns the return value from the job if the job actually completed
- * during the call, or -ECANCELED if it was canceled.
- */
-int block_job_cancel_sync(BlockJob *job);
-
-/**
  * stream_start:
  * @bs: Block device to operate on.
  * @base: Block device that will become the new base, or %NULL to
diff --git a/blockdev.c b/blockdev.c
index a3518f0..fe270cd 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -9,6 +9,7 @@
 
 #include "block.h"
 #include "blockdev.h"
+#include "blockjob.h"
 #include "monitor.h"
 #include "qerror.h"
 #include "qemu-option.h"
diff --git a/blockjob.c b/blockjob.c
new file mode 100644
index 0000000..accd646
--- /dev/null
+++ b/blockjob.c
@@ -0,0 +1,164 @@
+/*
+ * QEMU System Emulator block driver
+ *
+ * Copyright (c) 2011 IBM Corp.
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * 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 "config-host.h"
+#include "qemu-common.h"
+#include "trace.h"
+#include "monitor.h"
+#include "block.h"
+#include "blockjob.h"
+#include "block_int.h"
+#include "qjson.h"
+#include "qemu-coroutine.h"
+#include "qmp-commands.h"
+#include "qemu-timer.h"
+
+void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
+                       int64_t speed, BlockDriverCompletionFunc *cb,
+                       void *opaque, Error **errp)
+{
+    BlockJob *job;
+
+    if (bs->job || bdrv_in_use(bs)) {
+        error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bs));
+        return NULL;
+    }
+    bdrv_set_in_use(bs, 1);
+
+    job = g_malloc0(job_type->instance_size);
+    job->job_type      = job_type;
+    job->bs            = bs;
+    job->cb            = cb;
+    job->opaque        = opaque;
+    job->busy          = true;
+    bs->job = job;
+
+    /* Only set speed when necessary to avoid NotSupported error */
+    if (speed != 0) {
+        Error *local_err = NULL;
+
+        block_job_set_speed(job, speed, &local_err);
+        if (error_is_set(&local_err)) {
+            bs->job = NULL;
+            g_free(job);
+            bdrv_set_in_use(bs, 0);
+            error_propagate(errp, local_err);
+            return NULL;
+        }
+    }
+    return job;
+}
+
+void block_job_complete(BlockJob *job, int ret)
+{
+    BlockDriverState *bs = job->bs;
+
+    assert(bs->job == job);
+    job->cb(job->opaque, ret);
+    bs->job = NULL;
+    g_free(job);
+    bdrv_set_in_use(bs, 0);
+}
+
+void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
+{
+    Error *local_err = NULL;
+
+    if (!job->job_type->set_speed) {
+        error_set(errp, QERR_NOT_SUPPORTED);
+        return;
+    }
+    job->job_type->set_speed(job, speed, &local_err);
+    if (error_is_set(&local_err)) {
+        error_propagate(errp, local_err);
+        return;
+    }
+
+    job->speed = speed;
+}
+
+void block_job_cancel(BlockJob *job)
+{
+    job->cancelled = true;
+    if (job->co && !job->busy) {
+        qemu_coroutine_enter(job->co, NULL);
+    }
+}
+
+bool block_job_is_cancelled(BlockJob *job)
+{
+    return job->cancelled;
+}
+
+struct BlockCancelData {
+    BlockJob *job;
+    BlockDriverCompletionFunc *cb;
+    void *opaque;
+    bool cancelled;
+    int ret;
+};
+
+static void block_job_cancel_cb(void *opaque, int ret)
+{
+    struct BlockCancelData *data = opaque;
+
+    data->cancelled = block_job_is_cancelled(data->job);
+    data->ret = ret;
+    data->cb(data->opaque, ret);
+}
+
+int block_job_cancel_sync(BlockJob *job)
+{
+    struct BlockCancelData data;
+    BlockDriverState *bs = job->bs;
+
+    assert(bs->job == job);
+
+    /* Set up our own callback to store the result and chain to
+     * the original callback.
+     */
+    data.job = job;
+    data.cb = job->cb;
+    data.opaque = job->opaque;
+    data.ret = -EINPROGRESS;
+    job->cb = block_job_cancel_cb;
+    job->opaque = &data;
+    block_job_cancel(job);
+    while (data.ret == -EINPROGRESS) {
+        qemu_aio_wait();
+    }
+    return (data.cancelled && data.ret == 0) ? -ECANCELED : data.ret;
+}
+
+void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns)
+{
+    /* Check cancellation *before* setting busy = false, too!  */
+    if (!block_job_is_cancelled(job)) {
+        job->busy = false;
+        co_sleep_ns(clock, ns);
+        job->busy = true;
+    }
+}
+
diff --git a/blockjob.h b/blockjob.h
new file mode 100644
index 0000000..abcb2ee
--- /dev/null
+++ b/blockjob.h
@@ -0,0 +1,184 @@
+/*
+ * Declarations for long-running block device operations
+ *
+ * Copyright (c) 2011 IBM Corp.
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * 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.
+ */
+#ifndef BLOCKJOB_H
+#define BLOCKJOB_H 1
+
+#include "block.h"
+
+typedef struct BlockJob BlockJob;
+
+/**
+ * BlockJobType:
+ *
+ * A class type for block job objects.
+ */
+typedef struct BlockJobType {
+    /** Derived BlockJob struct size */
+    size_t instance_size;
+
+    /** String describing the operation, part of query-block-jobs QMP API */
+    const char *job_type;
+
+    /** Optional callback for job types that support setting a speed limit */
+    void (*set_speed)(BlockJob *job, int64_t speed, Error **errp);
+} BlockJobType;
+
+/**
+ * BlockJob:
+ *
+ * Long-running operation on a BlockDriverState.
+ */
+struct BlockJob {
+    /** The job type, including the job vtable.  */
+    const BlockJobType *job_type;
+
+    /** The block device on which the job is operating.  */
+    BlockDriverState *bs;
+
+    /**
+     * The coroutine that executes the job.  If not NULL, it is
+     * reentered when busy is false and the job is cancelled.
+     */
+    Coroutine *co;
+
+    /**
+     * Set to true if the job should cancel itself.  The flag must
+     * always be tested just before toggling the busy flag from false
+     * to true.  After a job has been cancelled, it should only yield
+     * if #qemu_aio_wait will ("sooner or later") reenter the coroutine.
+     */
+    bool cancelled;
+
+    /**
+     * Set to false by the job while it is in a quiescent state, where
+     * no I/O is pending and the job has yielded on any condition
+     * that is not detected by #qemu_aio_wait, such as a timer.
+     */
+    bool busy;
+
+    /** Offset that is published by the query-block-jobs QMP API */
+    int64_t offset;
+
+    /** Length that is published by the query-block-jobs QMP API */
+    int64_t len;
+
+    /** Speed that was set with @block_job_set_speed.  */
+    int64_t speed;
+
+    /** The completion function that will be called when the job completes.  */
+    BlockDriverCompletionFunc *cb;
+
+    /** The opaque value that is passed to the completion function.  */
+    void *opaque;
+};
+
+/**
+ * block_job_create:
+ * @job_type: The class object for the newly-created job.
+ * @bs: The block
+ * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
+ * @cb: Completion function for the job.
+ * @opaque: Opaque pointer value passed to @cb.
+ * @errp: Error object.
+ *
+ * Create a new long-running block device job and return it.  The job
+ * will call @cb asynchronously when the job completes.  Note that
+ * @bs may have been closed at the time the @cb it is called.  If
+ * this is the case, the job may be reported as either cancelled or
+ * completed.
+ *
+ * This function is not part of the public job interface; it should be
+ * called from a wrapper that is specific to the job type.
+ */
+void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
+                       int64_t speed, BlockDriverCompletionFunc *cb,
+                       void *opaque, Error **errp);
+
+/**
+ * block_job_sleep_ns:
+ * @job: The job that calls the function.
+ * @clock: The clock to sleep on.
+ * @ns: How many nanoseconds to stop for.
+ *
+ * Put the job to sleep (assuming that it wasn't canceled) for @ns
+ * nanoseconds.  Canceling the job will interrupt the wait immediately.
+ */
+void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns);
+
+/**
+ * block_job_complete:
+ * @job: The job being completed.
+ * @ret: The status code.
+ *
+ * Call the completion function that was registered at creation time, and
+ * free @job.
+ */
+void block_job_complete(BlockJob *job, int ret);
+
+/**
+ * block_job_set_speed:
+ * @job: The job to set the speed for.
+ * @speed: The new value
+ * @errp: Error object.
+ *
+ * Set a rate-limiting parameter for the job; the actual meaning may
+ * vary depending on the job type.
+ */
+void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
+
+/**
+ * block_job_cancel:
+ * @job: The job to be canceled.
+ *
+ * Asynchronously cancel the specified job.
+ */
+void block_job_cancel(BlockJob *job);
+
+/**
+ * block_job_is_cancelled:
+ * @job: The job being queried.
+ *
+ * Returns whether the job is scheduled for cancellation.
+ */
+bool block_job_is_cancelled(BlockJob *job);
+
+/**
+ * block_job_cancel_sync:
+ * @job: The job to be canceled.
+ *
+ * Synchronously cancel the job and wait for it to reach a quiescent
+ * state.  Note that the completion callback will still be called
+ * asynchronously, hence it is *not* valid to call #bdrv_delete
+ * immediately after #block_job_cancel_sync.  Users of block jobs
+ * will usually protect the BlockDriverState objects with a reference
+ * count, should this be a concern.
+ *
+ * Returns the return value from the job if the job actually completed
+ * during the call, or -ECANCELED if it was canceled.
+ */
+int block_job_cancel_sync(BlockJob *job);
+
+#endif
+
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 04/36] block: add block_job_query
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (2 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 03/36] block: move job APIs to separate files Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 05/36] block: add support for job pause/resume Paolo Bonzini
                   ` (31 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Extract it out of the implementation of info block-jobs.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockdev.c |   15 ++-------------
 blockjob.c |   10 ++++++++++
 blockjob.h |    8 ++++++++
 3 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index fe270cd..3dba4b8 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1170,19 +1170,8 @@ static void do_qmp_query_block_jobs_one(void *opaque, BlockDriverState *bs)
     BlockJob *job = bs->job;
 
     if (job) {
-        BlockJobInfoList *elem;
-        BlockJobInfo *info = g_new(BlockJobInfo, 1);
-        *info = (BlockJobInfo){
-            .type   = g_strdup(job->job_type->job_type),
-            .device = g_strdup(bdrv_get_device_name(bs)),
-            .len    = job->len,
-            .offset = job->offset,
-            .speed  = job->speed,
-        };
-
-        elem = g_new0(BlockJobInfoList, 1);
-        elem->value = info;
-
+        BlockJobInfoList *elem = g_new0(BlockJobInfoList, 1);
+        elem->value = block_job_query(bs->job);
         (*prev)->next = elem;
         *prev = elem;
     }
diff --git a/blockjob.c b/blockjob.c
index accd646..a947a6e 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -162,3 +162,13 @@ void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns)
     }
 }
 
+BlockJobInfo *block_job_query(BlockJob *job)
+{
+    BlockJobInfo *info = g_new(BlockJobInfo, 1);
+    info->type   = g_strdup(job->job_type->job_type);
+    info->device = g_strdup(bdrv_get_device_name(job->bs));
+    info->len    = job->len;
+    info->offset = job->offset;
+    info->speed  = job->speed;
+    return info;
+}
diff --git a/blockjob.h b/blockjob.h
index abcb2ee..32854b8 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -165,6 +165,14 @@ void block_job_cancel(BlockJob *job);
 bool block_job_is_cancelled(BlockJob *job);
 
 /**
+ * block_job_query:
+ * @job: The job to get information about.
+ *
+ * Return information about a job.
+ */
+BlockJobInfo *block_job_query(BlockJob *job);
+
+/**
  * block_job_cancel_sync:
  * @job: The job to be canceled.
  *
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 05/36] block: add support for job pause/resume
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (3 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 04/36] block: add block_job_query Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 17:22   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 06/36] qmp: add block-job-pause and block-job-resume Paolo Bonzini
                   ` (30 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Job pausing reuses the existing support for cancellable sleeps.  A pause
happens at the next sleeping point and lasts until the coroutine is
re-entered explicitly.  Cancellation was already doing a forced resume,
so implement it explicitly in terms of resume.

Paused jobs cannot be canceled without first resuming them.  This ensures
that I/O errors are never missed by management.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockdev.c       |    4 ++++
 blockjob.c       |   35 ++++++++++++++++++++++++++++++-----
 blockjob.h       |   30 ++++++++++++++++++++++++++++++
 qapi-schema.json |    5 ++++-
 qerror.c         |    4 ++++
 qerror.h         |    3 +++
 6 files changed, 75 insertions(+), 6 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index 3dba4b8..8bf659a 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1159,6 +1159,10 @@ void qmp_block_job_cancel(const char *device, Error **errp)
         error_set(errp, QERR_BLOCK_JOB_NOT_ACTIVE, device);
         return;
     }
+    if (job->paused) {
+        error_set(errp, QERR_BLOCK_JOB_PAUSED, device);
+        return;
+    }
 
     trace_qmp_block_job_cancel(job);
     block_job_cancel(job);
diff --git a/blockjob.c b/blockjob.c
index a947a6e..5d62191 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -99,14 +99,30 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
     job->speed = speed;
 }
 
-void block_job_cancel(BlockJob *job)
+void block_job_pause(BlockJob *job)
 {
-    job->cancelled = true;
+    job->paused = true;
+}
+
+bool block_job_is_paused(BlockJob *job)
+{
+    return job->paused;
+}
+
+void block_job_resume(BlockJob *job)
+{
+    job->paused = false;
     if (job->co && !job->busy) {
         qemu_coroutine_enter(job->co, NULL);
     }
 }
 
+void block_job_cancel(BlockJob *job)
+{
+    job->cancelled = true;
+    block_job_resume(job);
+}
+
 bool block_job_is_cancelled(BlockJob *job)
 {
     return job->cancelled;
@@ -154,12 +170,20 @@ int block_job_cancel_sync(BlockJob *job)
 
 void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns)
 {
+    assert(job->busy);
+
     /* Check cancellation *before* setting busy = false, too!  */
-    if (!block_job_is_cancelled(job)) {
-        job->busy = false;
+    if (block_job_is_cancelled(job)) {
+        return;
+    }
+
+    job->busy = false;
+    if (block_job_is_paused(job)) {
+        qemu_coroutine_yield();
+    } else {
         co_sleep_ns(clock, ns);
-        job->busy = true;
     }
+    job->busy = true;
 }
 
 BlockJobInfo *block_job_query(BlockJob *job)
@@ -168,6 +192,7 @@ BlockJobInfo *block_job_query(BlockJob *job)
     info->type   = g_strdup(job->job_type->job_type);
     info->device = g_strdup(bdrv_get_device_name(job->bs));
     info->len    = job->len;
+    info->paused = job->paused;
     info->offset = job->offset;
     info->speed  = job->speed;
     return info;
diff --git a/blockjob.h b/blockjob.h
index 32854b8..d35c65b 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -72,6 +72,12 @@ struct BlockJob {
     bool cancelled;
 
     /**
+     * Set to true if the job is either paused, or will pause itself
+     * as soon as possible (if busy == true).
+     */
+    bool paused;
+
+    /**
      * Set to false by the job while it is in a quiescent state, where
      * no I/O is pending and the job has yielded on any condition
      * that is not detected by #qemu_aio_wait, such as a timer.
@@ -173,6 +179,30 @@ bool block_job_is_cancelled(BlockJob *job);
 BlockJobInfo *block_job_query(BlockJob *job);
 
 /**
+ * block_job_pause:
+ * @job: The job to be paused.
+ *
+ * Asynchronously pause the specified job.
+ */
+void block_job_pause(BlockJob *job);
+
+/**
+ * block_job_resume:
+ * @job: The job to be resumed.
+ *
+ * Resume the specified job.
+ */
+void block_job_resume(BlockJob *job);
+
+/**
+ * block_job_is_paused:
+ * @job: The job being queried.
+ *
+ * Returns whether the job is currently paused.
+ */
+bool block_job_is_paused(BlockJob *job);
+
+/**
  * block_job_cancel_sync:
  * @job: The job to be canceled.
  *
diff --git a/qapi-schema.json b/qapi-schema.json
index 35c16f3..178549d 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -906,6 +906,8 @@
 #
 # @len: the maximum progress value
 #
+# @paused: whether the job is paused
+#
 # @offset: the current progress value
 #
 # @speed: the rate limit, bytes per second
@@ -914,7 +916,7 @@
 ##
 { 'type': 'BlockJobInfo',
   'data': {'type': 'str', 'device': 'str', 'len': 'int',
-           'offset': 'int', 'speed': 'int'} }
+           'offset': 'int', 'paused': 'bool', 'speed': 'int'} }
 
 ##
 # @query-block-jobs:
@@ -1680,6 +1682,7 @@
 #
 # Returns: Nothing on success
 #          If no background operation is active on this device, BlockJobNotActive
+#          If the job is currently paused, BlockJobPaused
 #
 # Since: 1.1
 ##
diff --git a/qerror.c b/qerror.c
index bc672a5..72183ec 100644
--- a/qerror.c
+++ b/qerror.c
@@ -64,6 +64,10 @@ static const QErrorStringTable qerror_table[] = {
         .desc      = "No active block job on device '%(name)'",
     },
     {
+        .error_fmt = QERR_BLOCK_JOB_PAUSED,
+        .desc      = "The block job for device '%(name)' is currently paused",
+    },
+    {
         .error_fmt = QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
         .desc      = "Block format '%(format)' used by device '%(name)' does not support feature '%(feature)'",
     },
diff --git a/qerror.h b/qerror.h
index 7cf7d22..d1baea0 100644
--- a/qerror.h
+++ b/qerror.h
@@ -67,6 +67,9 @@ QError *qobject_to_qerror(const QObject *obj);
 #define QERR_BLOCK_JOB_NOT_ACTIVE \
     "{ 'class': 'BlockJobNotActive', 'data': { 'name': %s } }"
 
+#define QERR_BLOCK_JOB_PAUSED \
+    "{ 'class': 'BlockJobPaused', 'data': { 'name': %s } }"
+
 #define QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED \
     "{ 'class': 'BlockFormatFeatureNotSupported', 'data': { 'format': %s, 'name': %s, 'feature': %s } }"
 
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 06/36] qmp: add block-job-pause and block-job-resume
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (4 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 05/36] block: add support for job pause/resume Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 17:32   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 07/36] qemu-iotests: add test for pausing a streaming operation Paolo Bonzini
                   ` (29 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Add QMP commands matching the functionality.

Paused jobs cannot be canceled without first resuming them.  This
ensures that I/O errors are never missed by management.  However, an
optional force argument can be specified to allow that.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockdev.c       |   35 +++++++++++++++++++++++++++++++++--
 hmp-commands.hx  |   35 ++++++++++++++++++++++++++++++++---
 hmp.c            |   23 ++++++++++++++++++++++-
 hmp.h            |    2 ++
 qapi-schema.json |   47 +++++++++++++++++++++++++++++++++++++++++++++--
 qmp-commands.hx  |   12 +++++++++++-
 trace-events     |    2 ++
 7 files changed, 147 insertions(+), 9 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index 8bf659a..1ea6e94 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1151,15 +1151,20 @@ void qmp_block_job_set_speed(const char *device, int64_t speed, Error **errp)
     block_job_set_speed(job, speed, errp);
 }
 
-void qmp_block_job_cancel(const char *device, Error **errp)
+void qmp_block_job_cancel(const char *device,
+                          bool has_force, bool force, Error **errp)
 {
     BlockJob *job = find_block_job(device);
 
+    if (!has_force) {
+        force = false;
+    }
+
     if (!job) {
         error_set(errp, QERR_BLOCK_JOB_NOT_ACTIVE, device);
         return;
     }
-    if (job->paused) {
+    if (job->paused && !force) {
         error_set(errp, QERR_BLOCK_JOB_PAUSED, device);
         return;
     }
@@ -1168,6 +1173,32 @@ void qmp_block_job_cancel(const char *device, Error **errp)
     block_job_cancel(job);
 }
 
+void qmp_block_job_pause(const char *device, Error **errp)
+{
+    BlockJob *job = find_block_job(device);
+
+    if (!job) {
+        error_set(errp, QERR_BLOCK_JOB_NOT_ACTIVE, device);
+        return;
+    }
+
+    trace_qmp_block_job_pause(job);
+    block_job_pause(job);
+}
+
+void qmp_block_job_resume(const char *device, Error **errp)
+{
+    BlockJob *job = find_block_job(device);
+
+    if (!job) {
+        error_set(errp, QERR_BLOCK_JOB_NOT_ACTIVE, device);
+        return;
+    }
+
+    trace_qmp_block_job_resume(job);
+    block_job_resume(job);
+}
+
 static void do_qmp_query_block_jobs_one(void *opaque, BlockDriverState *bs)
 {
     BlockJobInfoList **prev = opaque;
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 26a4d8d..836564c 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -99,9 +99,10 @@ ETEXI
 
     {
         .name       = "block_job_cancel",
-        .args_type  = "device:B",
-        .params     = "device",
-        .help       = "stop an active background block operation",
+        .args_type  = "force:-f,device:B",
+        .params     = "[-f] device",
+        .help       = "stop an active background block operation (use -f"
+                      "\n\t\t\t if the operation is currently paused)",
         .mhandler.cmd = hmp_block_job_cancel,
     },
 
@@ -112,6 +113,34 @@ Stop an active block streaming operation.
 ETEXI
 
     {
+        .name       = "block_job_pause",
+        .args_type  = "device:B",
+        .params     = "device",
+        .help       = "pause an active background block operation",
+        .mhandler.cmd = hmp_block_job_pause,
+    },
+
+STEXI
+@item block_job_pause
+@findex block_job_pause
+Pause an active block streaming operation.
+ETEXI
+
+    {
+        .name       = "block_job_resume",
+        .args_type  = "device:B",
+        .params     = "device",
+        .help       = "resume a paused background block operation",
+        .mhandler.cmd = hmp_block_job_resume,
+    },
+
+STEXI
+@item block_job_resume
+@findex block_job_resume
+Resume a paused block streaming operation.
+ETEXI
+
+    {
         .name       = "eject",
         .args_type  = "force:-f,device:B",
         .params     = "[-f] device",
diff --git a/hmp.c b/hmp.c
index b9cec1d..9f6b255 100644
--- a/hmp.c
+++ b/hmp.c
@@ -863,8 +863,29 @@ void hmp_block_job_cancel(Monitor *mon, const QDict *qdict)
 {
     Error *error = NULL;
     const char *device = qdict_get_str(qdict, "device");
+    bool force = qdict_get_try_bool(qdict, "force", 0);
 
-    qmp_block_job_cancel(device, &error);
+    qmp_block_job_cancel(device, true, force, &error);
+
+    hmp_handle_error(mon, &error);
+}
+
+void hmp_block_job_pause(Monitor *mon, const QDict *qdict)
+{
+    Error *error = NULL;
+    const char *device = qdict_get_str(qdict, "device");
+
+    qmp_block_job_pause(device, &error);
+
+    hmp_handle_error(mon, &error);
+}
+
+void hmp_block_job_resume(Monitor *mon, const QDict *qdict)
+{
+    Error *error = NULL;
+    const char *device = qdict_get_str(qdict, "device");
+
+    qmp_block_job_resume(device, &error);
 
     hmp_handle_error(mon, &error);
 }
diff --git a/hmp.h b/hmp.h
index 79d138d..6a16eec 100644
--- a/hmp.h
+++ b/hmp.h
@@ -59,6 +59,8 @@ void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
 void hmp_block_stream(Monitor *mon, const QDict *qdict);
 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
+void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
+void hmp_block_job_resume(Monitor *mon, const QDict *qdict);
 void hmp_migrate(Monitor *mon, const QDict *qdict);
 void hmp_device_del(Monitor *mon, const QDict *qdict);
 void hmp_dump_guest_memory(Monitor *mon, const QDict *qdict);
diff --git a/qapi-schema.json b/qapi-schema.json
index 178549d..c16e1ea 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1680,13 +1680,56 @@
 #
 # @device: the device name
 #
+# @force: #optional whether to allow cancellation of a paused job (default false)
+#
 # Returns: Nothing on success
 #          If no background operation is active on this device, BlockJobNotActive
-#          If the job is currently paused, BlockJobPaused
+#          If the job is paused and force is absent or false, BlockJobPaused
+#
+# Since: 1.1
+##
+{ 'command': 'block-job-cancel', 'data': { 'device': 'str', '*force': 'bool' } }
+
+##
+# @block-job-pause:
+#
+# Pause an active background block operation.
+#
+# This command returns immediately after marking the active background block
+# operation for pausing.  It is an error to call this command if no
+# operation is in progress.
+#
+# The operation will pause as soon as possible.  No event is emitted when
+# the operation is actually paused.  Cancelling a paused job automatically
+# resumes it.
+#
+# @device: the device name
+#
+# Returns: Nothing on success
+#          If no background operation is active on this device, DeviceNotActive
+#
+# Since: 1.1
+##
+{ 'command': 'block-job-pause', 'data': { 'device': 'str' } }
+
+##
+# @block-job-resume:
+#
+# Resume an active background block operation.
+#
+# This command returns immediately after resuming a paused background block
+# operation for cancellation.  It is an error to call this command if no
+# operation is in progress.
+#
+# @device: the device name
+#
+# Returns: Nothing on success
+#          If no background operation is active on this device, DeviceNotActive
+#          If cancellation already in progress, DeviceInUse
 #
 # Since: 1.1
 ##
-{ 'command': 'block-job-cancel', 'data': { 'device': 'str' } }
+{ 'command': 'block-job-resume', 'data': { 'device': 'str' } }
 
 ##
 # @ObjectTypeInfo:
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 2e1a38e..f20754a 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -730,10 +730,20 @@ EQMP
 
     {
         .name       = "block-job-cancel",
-        .args_type  = "device:B",
+        .args_type  = "device:B,force:b?",
         .mhandler.cmd_new = qmp_marshal_input_block_job_cancel,
     },
     {
+        .name       = "block-job-pause",
+        .args_type  = "device:B",
+        .mhandler.cmd_new = qmp_marshal_input_block_job_pause,
+    },
+    {
+        .name       = "block-job-resume",
+        .args_type  = "device:B",
+        .mhandler.cmd_new = qmp_marshal_input_block_job_resume,
+    },
+    {
         .name       = "transaction",
         .args_type  = "actions:q",
         .mhandler.cmd_new = qmp_marshal_input_transaction,
diff --git a/trace-events b/trace-events
index f70523c..25ebfe9 100644
--- a/trace-events
+++ b/trace-events
@@ -77,6 +77,8 @@ stream_start(void *bs, void *base, void *s, void *co, void *opaque) "bs %p base
 
 # blockdev.c
 qmp_block_job_cancel(void *job) "job %p"
+qmp_block_job_pause(void *job) "job %p"
+qmp_block_job_resume(void *job) "job %p"
 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"
 
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 07/36] qemu-iotests: add test for pausing a streaming operation
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (5 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 06/36] qmp: add block-job-pause and block-job-resume Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 08/36] block: rename block_job_complete to block_job_completed Paolo Bonzini
                   ` (28 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

These check that a paused streaming job does not advance its offset.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 tests/qemu-iotests/030 |   40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030
index cc671dd..0163945 100755
--- a/tests/qemu-iotests/030
+++ b/tests/qemu-iotests/030
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
+import time
 import os
 import iotests
 from iotests import qemu_img, qemu_io
@@ -98,6 +99,43 @@ class TestSingleDrive(ImageStreamingTestCase):
                          qemu_io('-c', 'map', test_img),
                          'image file map does not match backing file after streaming')
 
+    def test_stream_pause(self):
+        self.assert_no_active_streams()
+
+        result = self.vm.qmp('block-stream', device='drive0')
+        self.assert_qmp(result, 'return', {})
+
+        result = self.vm.qmp('block-job-pause', device='drive0')
+        self.assert_qmp(result, 'return', {})
+
+        time.sleep(1)
+        result = self.vm.qmp('query-block-jobs')
+        offset = self.dictpath(result, 'return[0]/offset')
+
+        time.sleep(1)
+        result = self.vm.qmp('query-block-jobs')
+        self.assert_qmp(result, 'return[0]/offset', offset)
+
+        result = self.vm.qmp('block-job-resume', device='drive0')
+        self.assert_qmp(result, 'return', {})
+
+        completed = False
+        while not completed:
+            for event in self.vm.get_qmp_events(wait=True):
+                if event['event'] == 'BLOCK_JOB_COMPLETED':
+                    self.assert_qmp(event, 'data/type', 'stream')
+                    self.assert_qmp(event, 'data/device', 'drive0')
+                    self.assert_qmp(event, 'data/offset', self.image_len)
+                    self.assert_qmp(event, 'data/len', self.image_len)
+                    completed = True
+
+        self.assert_no_active_streams()
+        self.vm.shutdown()
+
+        self.assertEqual(qemu_io('-c', 'map', backing_img),
+                         qemu_io('-c', 'map', test_img),
+                         'image file map does not match backing file after streaming')
+
     def test_stream_partial(self):
         self.assert_no_active_streams()
 
@@ -140,8 +178,6 @@ class TestStreamStop(ImageStreamingTestCase):
         os.remove(backing_img)
 
     def test_stream_stop(self):
-        import time
-
         self.assert_no_active_streams()
 
         result = self.vm.qmp('block-stream', device='drive0')
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 08/36] block: rename block_job_complete to block_job_completed
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (6 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 07/36] qemu-iotests: add test for pausing a streaming operation Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 09/36] block: rename BlockErrorAction, BlockQMPEventAction Paolo Bonzini
                   ` (27 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

The imperative will be used for the QMP command.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/stream.c |    4 ++--
 blockjob.c     |    2 +-
 blockjob.h     |    4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/block/stream.c b/block/stream.c
index 5b801b4..b3ede44 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -84,7 +84,7 @@ static void coroutine_fn stream_run(void *opaque)
 
     s->common.len = bdrv_getlength(bs);
     if (s->common.len < 0) {
-        block_job_complete(&s->common, s->common.len);
+        block_job_completed(&s->common, s->common.len);
         return;
     }
 
@@ -161,7 +161,7 @@ wait:
     }
 
     qemu_vfree(buf);
-    block_job_complete(&s->common, ret);
+    block_job_completed(&s->common, ret);
 }
 
 static void stream_set_speed(BlockJob *job, int64_t speed, Error **errp)
diff --git a/blockjob.c b/blockjob.c
index 5d62191..a18da3f 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -71,7 +71,7 @@ void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
     return job;
 }
 
-void block_job_complete(BlockJob *job, int ret)
+void block_job_completed(BlockJob *job, int ret)
 {
     BlockDriverState *bs = job->bs;
 
diff --git a/blockjob.h b/blockjob.h
index d35c65b..58e1b56 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -134,14 +134,14 @@ void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
 void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns);
 
 /**
- * block_job_complete:
+ * block_job_completed:
  * @job: The job being completed.
  * @ret: The status code.
  *
  * Call the completion function that was registered at creation time, and
  * free @job.
  */
-void block_job_complete(BlockJob *job, int ret);
+void block_job_completed(BlockJob *job, int ret);
 
 /**
  * block_job_set_speed:
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 09/36] block: rename BlockErrorAction, BlockQMPEventAction
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (7 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 08/36] block: rename block_job_complete to block_job_completed Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 10/36] block: move BlockdevOnError declaration to QAPI Paolo Bonzini
                   ` (26 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

We want to remove knowledge of BLOCK_ERR_STOP_ENOSPC from drivers, so
free up the nicer BlockErrorAction name for them.  Do not change the
enums.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c         |    8 ++++----
 block.h         |   12 ++++++------
 block_int.h     |    2 +-
 hw/ide/core.c   |    2 +-
 hw/scsi-disk.c  |    2 +-
 hw/virtio-blk.c |    2 +-
 6 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/block.c b/block.c
index c396d58..62a3dcf 100644
--- a/block.c
+++ b/block.c
@@ -1159,7 +1159,7 @@ void bdrv_set_dev_ops(BlockDriverState *bs, const BlockDevOps *ops,
 }
 
 void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
-                               BlockQMPEventAction action, int is_read)
+                               BlockErrorAction action, int is_read)
 {
     QObject *data;
     const char *action_str;
@@ -2385,14 +2385,14 @@ int bdrv_get_translation_hint(BlockDriverState *bs)
     return bs->translation;
 }
 
-void bdrv_set_on_error(BlockDriverState *bs, BlockErrorAction on_read_error,
-                       BlockErrorAction on_write_error)
+void bdrv_set_on_error(BlockDriverState *bs, BlockdevOnError on_read_error,
+                       BlockdevOnError on_write_error)
 {
     bs->on_read_error = on_read_error;
     bs->on_write_error = on_write_error;
 }
 
-BlockErrorAction bdrv_get_on_error(BlockDriverState *bs, int is_read)
+BlockdevOnError bdrv_get_on_error(BlockDriverState *bs, int is_read)
 {
     return is_read ? bs->on_read_error : bs->on_write_error;
 }
diff --git a/block.h b/block.h
index 298c616..bb1ae42 100644
--- a/block.h
+++ b/block.h
@@ -91,11 +91,11 @@ typedef struct BlockDevOps {
 typedef enum {
     BLOCK_ERR_REPORT, BLOCK_ERR_IGNORE, BLOCK_ERR_STOP_ENOSPC,
     BLOCK_ERR_STOP_ANY
-} BlockErrorAction;
+} BlockdevOnError;
 
 typedef enum {
     BDRV_ACTION_REPORT, BDRV_ACTION_IGNORE, BDRV_ACTION_STOP
-} BlockQMPEventAction;
+} BlockErrorAction;
 
 void bdrv_iostatus_enable(BlockDriverState *bs);
 void bdrv_iostatus_reset(BlockDriverState *bs);
@@ -103,7 +103,7 @@ void bdrv_iostatus_disable(BlockDriverState *bs);
 bool bdrv_iostatus_is_enabled(const BlockDriverState *bs);
 void bdrv_iostatus_set_err(BlockDriverState *bs, int error);
 void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
-                               BlockQMPEventAction action, int is_read);
+                               BlockErrorAction action, int is_read);
 void bdrv_info_print(Monitor *mon, const QObject *data);
 void bdrv_info(Monitor *mon, QObject **ret_data);
 void bdrv_stats_print(Monitor *mon, const QObject *data);
@@ -288,9 +288,9 @@ void bdrv_get_floppy_geometry_hint(BlockDriverState *bs, int *nb_heads,
                                    FDriveType drive_in, FDriveType *drive,
                                    FDriveRate *rate);
 int bdrv_get_translation_hint(BlockDriverState *bs);
-void bdrv_set_on_error(BlockDriverState *bs, BlockErrorAction on_read_error,
-                       BlockErrorAction on_write_error);
-BlockErrorAction bdrv_get_on_error(BlockDriverState *bs, int is_read);
+void bdrv_set_on_error(BlockDriverState *bs, BlockdevOnError on_read_error,
+                       BlockdevOnError on_write_error);
+BlockdevOnError bdrv_get_on_error(BlockDriverState *bs, int is_read);
 int bdrv_is_read_only(BlockDriverState *bs);
 int bdrv_is_sg(BlockDriverState *bs);
 int bdrv_enable_write_cache(BlockDriverState *bs);
diff --git a/block_int.h b/block_int.h
index 4fef4f3..afe3dd1 100644
--- a/block_int.h
+++ b/block_int.h
@@ -254,7 +254,7 @@ struct BlockDriverState {
     /* NOTE: the following infos are only hints for real hardware
        drivers. They are not used by the block driver */
     int cyls, heads, secs, translation;
-    BlockErrorAction on_read_error, on_write_error;
+    BlockdevOnError on_read_error, on_write_error;
     bool iostatus_enabled;
     BlockDeviceIoStatus iostatus;
     char device_name[32];
diff --git a/hw/ide/core.c b/hw/ide/core.c
index f28229a..f78b282 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -558,7 +558,7 @@ void ide_dma_error(IDEState *s)
 static int ide_handle_rw_error(IDEState *s, int error, int op)
 {
     int is_read = (op & BM_STATUS_RETRY_READ);
-    BlockErrorAction action = bdrv_get_on_error(s->bs, is_read);
+    BlockdevOnError action = bdrv_get_on_error(s->bs, is_read);
 
     if (action == BLOCK_ERR_IGNORE) {
         bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_IGNORE, is_read);
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c
index 9197b08..76c32e4 100644
--- a/hw/scsi-disk.c
+++ b/hw/scsi-disk.c
@@ -390,7 +390,7 @@ static int scsi_handle_rw_error(SCSIDiskReq *r, int error)
 {
     int is_read = (r->req.cmd.xfer == SCSI_XFER_FROM_DEV);
     SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
-    BlockErrorAction action = bdrv_get_on_error(s->qdev.conf.bs, is_read);
+    BlockdevOnError action = bdrv_get_on_error(s->qdev.conf.bs, is_read);
 
     if (action == BLOCK_ERR_IGNORE) {
         bdrv_emit_qmp_error_event(s->qdev.conf.bs, BDRV_ACTION_IGNORE, is_read);
diff --git a/hw/virtio-blk.c b/hw/virtio-blk.c
index fe07746..d4afb20 100644
--- a/hw/virtio-blk.c
+++ b/hw/virtio-blk.c
@@ -65,7 +65,7 @@ static void virtio_blk_req_complete(VirtIOBlockReq *req, int status)
 static int virtio_blk_handle_rw_error(VirtIOBlockReq *req, int error,
     int is_read)
 {
-    BlockErrorAction action = bdrv_get_on_error(req->dev->bs, is_read);
+    BlockdevOnError action = bdrv_get_on_error(req->dev->bs, is_read);
     VirtIOBlock *s = req->dev;
 
     if (action == BLOCK_ERR_IGNORE) {
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 10/36] block: move BlockdevOnError declaration to QAPI
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (8 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 09/36] block: rename BlockErrorAction, BlockQMPEventAction Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 11/36] block: reorganize io error code Paolo Bonzini
                   ` (25 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

This will let block-stream reuse the enum.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c           |    6 +++---
 block.h           |    5 -----
 blockdev.c        |   12 ++++++------
 hw/fdc.c          |    4 ++--
 hw/ide/core.c     |    6 +++---
 hw/scsi-disk.c    |    6 +++---
 hw/scsi-generic.c |    4 ++--
 hw/virtio-blk.c   |    6 +++---
 qapi-schema.json  |   23 +++++++++++++++++++++++
 9 files changed, 45 insertions(+), 27 deletions(-)

diff --git a/block.c b/block.c
index 62a3dcf..878b712 100644
--- a/block.c
+++ b/block.c
@@ -4079,9 +4079,9 @@ void bdrv_iostatus_enable(BlockDriverState *bs)
 bool bdrv_iostatus_is_enabled(const BlockDriverState *bs)
 {
     return (bs->iostatus_enabled &&
-           (bs->on_write_error == BLOCK_ERR_STOP_ENOSPC ||
-            bs->on_write_error == BLOCK_ERR_STOP_ANY    ||
-            bs->on_read_error == BLOCK_ERR_STOP_ANY));
+           (bs->on_write_error == BLOCKDEV_ON_ERROR_ENOSPC ||
+            bs->on_write_error == BLOCKDEV_ON_ERROR_STOP   ||
+            bs->on_read_error == BLOCKDEV_ON_ERROR_STOP));
 }
 
 void bdrv_iostatus_disable(BlockDriverState *bs)
diff --git a/block.h b/block.h
index bb1ae42..32f8fc5 100644
--- a/block.h
+++ b/block.h
@@ -89,11 +89,6 @@ typedef struct BlockDevOps {
 #define BDRV_SECTOR_MASK   ~(BDRV_SECTOR_SIZE - 1)
 
 typedef enum {
-    BLOCK_ERR_REPORT, BLOCK_ERR_IGNORE, BLOCK_ERR_STOP_ENOSPC,
-    BLOCK_ERR_STOP_ANY
-} BlockdevOnError;
-
-typedef enum {
     BDRV_ACTION_REPORT, BDRV_ACTION_IGNORE, BDRV_ACTION_STOP
 } BlockErrorAction;
 
diff --git a/blockdev.c b/blockdev.c
index 1ea6e94..51bc488 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -241,13 +241,13 @@ static void drive_put_ref_bh_schedule(DriveInfo *dinfo)
 static int parse_block_error_action(const char *buf, int is_read)
 {
     if (!strcmp(buf, "ignore")) {
-        return BLOCK_ERR_IGNORE;
+        return BLOCKDEV_ON_ERROR_IGNORE;
     } else if (!is_read && !strcmp(buf, "enospc")) {
-        return BLOCK_ERR_STOP_ENOSPC;
+        return BLOCKDEV_ON_ERROR_ENOSPC;
     } else if (!strcmp(buf, "stop")) {
-        return BLOCK_ERR_STOP_ANY;
+        return BLOCKDEV_ON_ERROR_STOP;
     } else if (!strcmp(buf, "report")) {
-        return BLOCK_ERR_REPORT;
+        return BLOCKDEV_ON_ERROR_REPORT;
     } else {
         error_report("'%s' invalid %s error action",
                      buf, is_read ? "read" : "write");
@@ -432,7 +432,7 @@ DriveInfo *drive_init(QemuOpts *opts, int default_to_scsi)
         return NULL;
     }
 
-    on_write_error = BLOCK_ERR_STOP_ENOSPC;
+    on_write_error = BLOCKDEV_ON_ERROR_ENOSPC;
     if ((buf = qemu_opt_get(opts, "werror")) != NULL) {
         if (type != IF_IDE && type != IF_SCSI && type != IF_VIRTIO && type != IF_NONE) {
             error_report("werror is not supported by this bus type");
@@ -445,7 +445,7 @@ DriveInfo *drive_init(QemuOpts *opts, int default_to_scsi)
         }
     }
 
-    on_read_error = BLOCK_ERR_REPORT;
+    on_read_error = BLOCKDEV_ON_ERROR_REPORT;
     if ((buf = qemu_opt_get(opts, "rerror")) != NULL) {
         if (type != IF_IDE && type != IF_VIRTIO && type != IF_SCSI && type != IF_NONE) {
             error_report("rerror is not supported by this bus type");
diff --git a/hw/fdc.c b/hw/fdc.c
index bfa4e68..1c266f1 100644
--- a/hw/fdc.c
+++ b/hw/fdc.c
@@ -1869,11 +1869,11 @@ static int fdctrl_connect_drives(FDCtrl *fdctrl)
         drive->fdctrl = fdctrl;
 
         if (drive->bs) {
-            if (bdrv_get_on_error(drive->bs, 0) != BLOCK_ERR_STOP_ENOSPC) {
+            if (bdrv_get_on_error(drive->bs, 0) != BLOCKDEV_ON_ERROR_ENOSPC) {
                 error_report("fdc doesn't support drive option werror");
                 return -1;
             }
-            if (bdrv_get_on_error(drive->bs, 1) != BLOCK_ERR_REPORT) {
+            if (bdrv_get_on_error(drive->bs, 1) != BLOCKDEV_ON_ERROR_REPORT) {
                 error_report("fdc doesn't support drive option rerror");
                 return -1;
             }
diff --git a/hw/ide/core.c b/hw/ide/core.c
index f78b282..2fb584f 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -560,13 +560,13 @@ static int ide_handle_rw_error(IDEState *s, int error, int op)
     int is_read = (op & BM_STATUS_RETRY_READ);
     BlockdevOnError action = bdrv_get_on_error(s->bs, is_read);
 
-    if (action == BLOCK_ERR_IGNORE) {
+    if (action == BLOCKDEV_ON_ERROR_IGNORE) {
         bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_IGNORE, is_read);
         return 0;
     }
 
-    if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC)
-            || action == BLOCK_ERR_STOP_ANY) {
+    if ((error == ENOSPC && action == BLOCKDEV_ON_ERROR_ENOSPC)
+            || action == BLOCKDEV_ON_ERROR_STOP) {
         s->bus->dma->ops->set_unit(s->bus->dma, s->unit);
         s->bus->error_status = op;
         bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_STOP, is_read);
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c
index 76c32e4..b196328 100644
--- a/hw/scsi-disk.c
+++ b/hw/scsi-disk.c
@@ -392,13 +392,13 @@ static int scsi_handle_rw_error(SCSIDiskReq *r, int error)
     SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
     BlockdevOnError action = bdrv_get_on_error(s->qdev.conf.bs, is_read);
 
-    if (action == BLOCK_ERR_IGNORE) {
+    if (action == BLOCKDEV_ON_ERROR_IGNORE) {
         bdrv_emit_qmp_error_event(s->qdev.conf.bs, BDRV_ACTION_IGNORE, is_read);
         return 0;
     }
 
-    if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC)
-            || action == BLOCK_ERR_STOP_ANY) {
+    if ((error == ENOSPC && action == BLOCKDEV_ON_ERROR_ENOSPC)
+            || action == BLOCKDEV_ON_ERROR_STOP) {
 
         bdrv_emit_qmp_error_event(s->qdev.conf.bs, BDRV_ACTION_STOP, is_read);
         vm_stop(RUN_STATE_IO_ERROR);
diff --git a/hw/scsi-generic.c b/hw/scsi-generic.c
index d856d23..380925d 100644
--- a/hw/scsi-generic.c
+++ b/hw/scsi-generic.c
@@ -406,11 +406,11 @@ static int scsi_generic_initfn(SCSIDevice *s)
         return -1;
     }
 
-    if (bdrv_get_on_error(s->conf.bs, 0) != BLOCK_ERR_STOP_ENOSPC) {
+    if (bdrv_get_on_error(s->conf.bs, 0) != BLOCKDEV_ON_ERROR_ENOSPC) {
         error_report("Device doesn't support drive option werror");
         return -1;
     }
-    if (bdrv_get_on_error(s->conf.bs, 1) != BLOCK_ERR_REPORT) {
+    if (bdrv_get_on_error(s->conf.bs, 1) != BLOCKDEV_ON_ERROR_REPORT) {
         error_report("Device doesn't support drive option rerror");
         return -1;
     }
diff --git a/hw/virtio-blk.c b/hw/virtio-blk.c
index d4afb20..29f93af 100644
--- a/hw/virtio-blk.c
+++ b/hw/virtio-blk.c
@@ -68,13 +68,13 @@ static int virtio_blk_handle_rw_error(VirtIOBlockReq *req, int error,
     BlockdevOnError action = bdrv_get_on_error(req->dev->bs, is_read);
     VirtIOBlock *s = req->dev;
 
-    if (action == BLOCK_ERR_IGNORE) {
+    if (action == BLOCKDEV_ON_ERROR_IGNORE) {
         bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_IGNORE, is_read);
         return 0;
     }
 
-    if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC)
-            || action == BLOCK_ERR_STOP_ANY) {
+    if ((error == ENOSPC && action == BLOCKDEV_ON_ERROR_ENOSPC)
+            || action == BLOCKDEV_ON_ERROR_STOP) {
         req->next = s->rq;
         s->rq = req;
         bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_STOP, is_read);
diff --git a/qapi-schema.json b/qapi-schema.json
index c16e1ea..b760bc8 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -896,6 +896,29 @@
 { 'command': 'query-pci', 'returns': ['PciInfo'] }
 
 ##
+# @BlockdevOnError:
+#
+# An enumeration of possible behaviors for errors on I/O operations.
+# The exact meaning depends on whether the I/O was initiated by a guest
+# or by a block job
+#
+# @report: for guest operations, report the error to the guest;
+#          for jobs, cancel the job
+#
+# @ignore: ignore the error, only report a QMP event (BLOCK_IO_ERROR
+#          or BLOCK_JOB_ERROR)
+#
+# @stop: for guest operations, stop the virtual machine;
+#        for jobs, pause the job
+#
+# @enospc: same as @stop on ENOSPC, same as @report otherwise.
+#
+# Since: 1.2
+##
+{ 'enum': 'BlockdevOnError',
+  'data': ['report', 'ignore', 'enospc', 'stop'] }
+
+##
 # @BlockJobInfo:
 #
 # Information about a long-running block device operation.
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 11/36] block: reorganize io error code
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (9 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 10/36] block: move BlockdevOnError declaration to QAPI Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 12/36] block: sort BlockDeviceIoStatus errors by severity Paolo Bonzini
                   ` (24 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Move the common part of IDE/SCSI/virtio error handling to the block
layer.  The same scheme will be used for errors in block jobs.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c         |   46 ++++++++++++++++++++++++++++++++++++++--------
 block.h         |    5 +++--
 hw/ide/core.c   |   20 +++++---------------
 hw/scsi-disk.c  |   23 +++++++----------------
 hw/virtio-blk.c |   19 +++++--------------
 qemu-tool.c     |    6 ++++++
 6 files changed, 64 insertions(+), 55 deletions(-)

diff --git a/block.c b/block.c
index 878b712..4011ce4 100644
--- a/block.c
+++ b/block.c
@@ -29,6 +29,7 @@
 #include "blockjob.h"
 #include "module.h"
 #include "qjson.h"
+#include "sysemu.h"
 #include "qemu-coroutine.h"
 #include "qmp-commands.h"
 #include "qemu-timer.h"
@@ -1158,8 +1159,8 @@ void bdrv_set_dev_ops(BlockDriverState *bs, const BlockDevOps *ops,
     }
 }
 
-void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
-                               BlockErrorAction action, int is_read)
+static void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
+                                      BlockErrorAction action, int is_read)
 {
     QObject *data;
     const char *action_str;
@@ -2397,6 +2398,39 @@ BlockdevOnError bdrv_get_on_error(BlockDriverState *bs, int is_read)
     return is_read ? bs->on_read_error : bs->on_write_error;
 }
 
+BlockErrorAction bdrv_get_error_action(BlockDriverState *bs, int is_read, int error)
+{
+    BlockdevOnError on_err = is_read ? bs->on_read_error : bs->on_write_error;
+
+    switch (on_err) {
+    case BLOCKDEV_ON_ERROR_ENOSPC:
+        return (error == ENOSPC) ? BDRV_ACTION_STOP : BDRV_ACTION_REPORT;
+    case BLOCKDEV_ON_ERROR_STOP:
+        return BDRV_ACTION_STOP;
+    case BLOCKDEV_ON_ERROR_REPORT:
+        return BDRV_ACTION_REPORT;
+    case BLOCKDEV_ON_ERROR_IGNORE:
+        return BDRV_ACTION_IGNORE;
+    default:
+        abort();
+    }
+}
+
+/* This is done by device models because, while the block layer knows
+ * about the error, it does not know whether an operation comes from
+ * the device or the block layer (from a job, for example).
+ */
+void bdrv_error_action(BlockDriverState *bs, BlockErrorAction action,
+                       int is_read, int error)
+{
+    assert(error >= 0);
+    bdrv_emit_qmp_error_event(bs, action, is_read);
+    if (action == BDRV_ACTION_STOP) {
+        vm_stop(RUN_STATE_IO_ERROR);
+        bdrv_iostatus_set_err(bs, error);
+    }
+}
+
 int bdrv_is_read_only(BlockDriverState *bs)
 {
     return bs->read_only;
@@ -4096,14 +4130,10 @@ void bdrv_iostatus_reset(BlockDriverState *bs)
     }
 }
 
-/* XXX: Today this is set by device models because it makes the implementation
-   quite simple. However, the block layer knows about the error, so it's
-   possible to implement this without device models being involved */
 void bdrv_iostatus_set_err(BlockDriverState *bs, int error)
 {
-    if (bdrv_iostatus_is_enabled(bs) &&
-        bs->iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
-        assert(error >= 0);
+    assert(bdrv_iostatus_is_enabled(bs));
+    if (bs->iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
         bs->iostatus = error == ENOSPC ? BLOCK_DEVICE_IO_STATUS_NOSPACE :
                                          BLOCK_DEVICE_IO_STATUS_FAILED;
     }
diff --git a/block.h b/block.h
index 32f8fc5..32b075c 100644
--- a/block.h
+++ b/block.h
@@ -97,8 +97,6 @@ void bdrv_iostatus_reset(BlockDriverState *bs);
 void bdrv_iostatus_disable(BlockDriverState *bs);
 bool bdrv_iostatus_is_enabled(const BlockDriverState *bs);
 void bdrv_iostatus_set_err(BlockDriverState *bs, int error);
-void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
-                               BlockErrorAction action, int is_read);
 void bdrv_info_print(Monitor *mon, const QObject *data);
 void bdrv_info(Monitor *mon, QObject **ret_data);
 void bdrv_stats_print(Monitor *mon, const QObject *data);
@@ -286,6 +284,9 @@ int bdrv_get_translation_hint(BlockDriverState *bs);
 void bdrv_set_on_error(BlockDriverState *bs, BlockdevOnError on_read_error,
                        BlockdevOnError on_write_error);
 BlockdevOnError bdrv_get_on_error(BlockDriverState *bs, int is_read);
+BlockErrorAction bdrv_get_error_action(BlockDriverState *bs, int is_read, int error);
+void bdrv_error_action(BlockDriverState *bs, BlockErrorAction action,
+                       int is_read, int error);
 int bdrv_is_read_only(BlockDriverState *bs);
 int bdrv_is_sg(BlockDriverState *bs);
 int bdrv_enable_write_cache(BlockDriverState *bs);
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 2fb584f..b71fbda 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -558,31 +558,21 @@ void ide_dma_error(IDEState *s)
 static int ide_handle_rw_error(IDEState *s, int error, int op)
 {
     int is_read = (op & BM_STATUS_RETRY_READ);
-    BlockdevOnError action = bdrv_get_on_error(s->bs, is_read);
+    BlockErrorAction action = bdrv_get_error_action(s->bs, is_read, error);
 
-    if (action == BLOCKDEV_ON_ERROR_IGNORE) {
-        bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_IGNORE, is_read);
-        return 0;
-    }
-
-    if ((error == ENOSPC && action == BLOCKDEV_ON_ERROR_ENOSPC)
-            || action == BLOCKDEV_ON_ERROR_STOP) {
+    if (action == BDRV_ACTION_STOP) {
         s->bus->dma->ops->set_unit(s->bus->dma, s->unit);
         s->bus->error_status = op;
-        bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_STOP, is_read);
-        vm_stop(RUN_STATE_IO_ERROR);
-        bdrv_iostatus_set_err(s->bs, error);
-    } else {
+    } else if (action == BDRV_ACTION_REPORT) {
         if (op & BM_STATUS_DMA_RETRY) {
             dma_buf_commit(s);
             ide_dma_error(s);
         } else {
             ide_rw_error(s);
         }
-        bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_REPORT, is_read);
     }
-
-    return 1;
+    bdrv_error_action(s->bs, action, is_read, error);
+    return action != BDRV_ACTION_IGNORE;
 }
 
 void ide_dma_cb(void *opaque, int ret)
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c
index b196328..a58b109 100644
--- a/hw/scsi-disk.c
+++ b/hw/scsi-disk.c
@@ -390,21 +390,9 @@ static int scsi_handle_rw_error(SCSIDiskReq *r, int error)
 {
     int is_read = (r->req.cmd.xfer == SCSI_XFER_FROM_DEV);
     SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
-    BlockdevOnError action = bdrv_get_on_error(s->qdev.conf.bs, is_read);
+    BlockErrorAction action = bdrv_get_error_action(s->qdev.conf.bs, is_read, error);
 
-    if (action == BLOCKDEV_ON_ERROR_IGNORE) {
-        bdrv_emit_qmp_error_event(s->qdev.conf.bs, BDRV_ACTION_IGNORE, is_read);
-        return 0;
-    }
-
-    if ((error == ENOSPC && action == BLOCKDEV_ON_ERROR_ENOSPC)
-            || action == BLOCKDEV_ON_ERROR_STOP) {
-
-        bdrv_emit_qmp_error_event(s->qdev.conf.bs, BDRV_ACTION_STOP, is_read);
-        vm_stop(RUN_STATE_IO_ERROR);
-        bdrv_iostatus_set_err(s->qdev.conf.bs, error);
-        scsi_req_retry(&r->req);
-    } else {
+    if (action == BDRV_ACTION_REPORT) {
         switch (error) {
         case ENOMEDIUM:
             scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
@@ -419,9 +407,12 @@ static int scsi_handle_rw_error(SCSIDiskReq *r, int error)
             scsi_check_condition(r, SENSE_CODE(IO_ERROR));
             break;
         }
-        bdrv_emit_qmp_error_event(s->qdev.conf.bs, BDRV_ACTION_REPORT, is_read);
     }
-    return 1;
+    bdrv_error_action(s->qdev.conf.bs, action, is_read, error);
+    if (action == BDRV_ACTION_STOP) {
+        scsi_req_retry(&r->req);
+    }
+    return action != BDRV_ACTION_IGNORE;
 }
 
 static void scsi_write_complete(void * opaque, int ret)
diff --git a/hw/virtio-blk.c b/hw/virtio-blk.c
index 29f93af..2599326 100644
--- a/hw/virtio-blk.c
+++ b/hw/virtio-blk.c
@@ -65,29 +65,20 @@ static void virtio_blk_req_complete(VirtIOBlockReq *req, int status)
 static int virtio_blk_handle_rw_error(VirtIOBlockReq *req, int error,
     int is_read)
 {
-    BlockdevOnError action = bdrv_get_on_error(req->dev->bs, is_read);
+    BlockErrorAction action = bdrv_get_error_action(req->dev->bs, is_read, error);
     VirtIOBlock *s = req->dev;
 
-    if (action == BLOCKDEV_ON_ERROR_IGNORE) {
-        bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_IGNORE, is_read);
-        return 0;
-    }
-
-    if ((error == ENOSPC && action == BLOCKDEV_ON_ERROR_ENOSPC)
-            || action == BLOCKDEV_ON_ERROR_STOP) {
+    if (action == BDRV_ACTION_STOP) {
         req->next = s->rq;
         s->rq = req;
-        bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_STOP, is_read);
-        vm_stop(RUN_STATE_IO_ERROR);
-        bdrv_iostatus_set_err(s->bs, error);
-    } else {
+    } else if (action == BDRV_ACTION_REPORT) {
         virtio_blk_req_complete(req, VIRTIO_BLK_S_IOERR);
         bdrv_acct_done(s->bs, &req->acct);
         g_free(req);
-        bdrv_emit_qmp_error_event(s->bs, BDRV_ACTION_REPORT, is_read);
     }
 
-    return 1;
+    bdrv_error_action(s->bs, action, is_read, error);
+    return action != BDRV_ACTION_IGNORE;
 }
 
 static void virtio_blk_rw_complete(void *opaque, int ret)
diff --git a/qemu-tool.c b/qemu-tool.c
index 07fc4f2..fd754d6 100644
--- a/qemu-tool.c
+++ b/qemu-tool.c
@@ -19,6 +19,7 @@
 #include "qemu-log.h"
 #include "migration.h"
 #include "main-loop.h"
+#include "sysemu.h"
 #include "qemu_socket.h"
 #include "slirp/libslirp.h"
 
@@ -34,6 +35,11 @@ struct QEMUBH
 
 Monitor *cur_mon;
 
+void vm_stop(RunState state)
+{
+    abort();
+}
+
 int monitor_cur_is_qmp(void)
 {
     return 0;
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 12/36] block: sort BlockDeviceIoStatus errors by severity
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (10 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 11/36] block: reorganize io error code Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 17:45   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 13/36] block: introduce block job error Paolo Bonzini
                   ` (23 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c          |   11 ++++++++---
 qapi-schema.json |    2 +-
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/block.c b/block.c
index 4011ce4..2f6384a 100644
--- a/block.c
+++ b/block.c
@@ -4133,9 +4133,14 @@ void bdrv_iostatus_reset(BlockDriverState *bs)
 void bdrv_iostatus_set_err(BlockDriverState *bs, int error)
 {
     assert(bdrv_iostatus_is_enabled(bs));
-    if (bs->iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
-        bs->iostatus = error == ENOSPC ? BLOCK_DEVICE_IO_STATUS_NOSPACE :
-                                         BLOCK_DEVICE_IO_STATUS_FAILED;
+    BlockDeviceIoStatus new_status =
+        (error == ENOSPC ? BLOCK_DEVICE_IO_STATUS_NOSPACE :
+                           BLOCK_DEVICE_IO_STATUS_FAILED);
+
+    /* iostatus values are sorted from less severe to most severe
+     * (ok, nospace, failed).  */
+    if (bs->iostatus < new_status) {
+        bs->iostatus = new_status;
     }
 }
 
diff --git a/qapi-schema.json b/qapi-schema.json
index b760bc8..d09d267 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -429,7 +429,7 @@
 #
 # Since: 1.0
 ##
-{ 'enum': 'BlockDeviceIoStatus', 'data': [ 'ok', 'failed', 'nospace' ] }
+{ 'enum': 'BlockDeviceIoStatus', 'data': [ 'ok', 'nospace', 'failed' ] }
 
 ##
 # @BlockInfo:
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 13/36] block: introduce block job error
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (11 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 12/36] block: sort BlockDeviceIoStatus errors by severity Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 17:50   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 14/36] stream: add on_error argument Paolo Bonzini
                   ` (22 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

The following behaviors are possible:

'report': The behavior is the same as in 1.1.  An I/O error,
respectively during a read or a write, will complete the job immediately
with an error code.

'ignore': An I/O error, respectively during a read or a write, will be
ignored.  For streaming, the job will complete with an error and the
backing file will be left in place.  For mirroring, the sector will be
marked again as dirty and re-examined later.

'stop': The job will be paused and the job iostatus will be set to
failed or nospace, while the VM will keep running.  This can only be
specified if the block device has rerror=stop and werror=stop or enospc.

'enospc': Behaves as 'stop' for ENOSPC errors, 'report' for others.

In all cases, even for 'report', the I/O error is reported as a QMP
event BLOCK_JOB_ERROR, with the same arguments as BLOCK_IO_ERROR.

It is possible that while stopping the VM a BLOCK_IO_ERROR event will be
reported and will clobber the event from BLOCK_JOB_ERROR, or vice versa.
This is not really avoidable since stopping the VM completes all pending
I/O requests.  In fact, it is already possible now that a series of
BLOCK_IO_ERROR events are reported with rerror=stop, because vm_stop
calls bdrv_drain_all and this can generate further errors.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 QMP/qmp-events.txt |   27 +++++++++++++++++++++++
 block.c            |    9 ++++----
 block_int.h        |    4 ++++
 blockjob.c         |   62 +++++++++++++++++++++++++++++++++++++++++++++++-----
 blockjob.h         |   17 ++++++++++++++
 monitor.c          |    1 +
 monitor.h          |    1 +
 qapi-schema.json   |    5 ++++-
 8 files changed, 115 insertions(+), 11 deletions(-)

diff --git a/QMP/qmp-events.txt b/QMP/qmp-events.txt
index 9286af5..6e09ee6 100644
--- a/QMP/qmp-events.txt
+++ b/QMP/qmp-events.txt
@@ -335,3 +335,30 @@ Example:
                "len": 10737418240, "offset": 134217728,
                "speed": 0 },
      "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
+
+
+BLOCK_JOB_ERROR
+---------------
+
+Emitted when a block job encounters an error.
+
+Data:
+
+- "device": device name (json-string)
+- "operation": I/O operation (json-string, "read" or "write")
+- "action": action that has been taken, it's one of the following (json-string):
+    "ignore": error has been ignored, the job may fail later
+    "report": error will be reported and the job canceled
+    "stop": error caused job to be paused
+
+Example:
+
+{ "event": "BLOCK_JOB_ERROR",
+    "data": { "device": "ide0-hd1",
+              "operation": "write",
+              "action": "stop" },
+    "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+
+Note: If action is "stop", a STOP event will eventually follow the
+BLOCK_JOB_ERROR event.  If action is "report", a BLOCK_JOB_CANCELLED
+event will eventually follow the BLOCK_JOB_ERROR event.
diff --git a/block.c b/block.c
index 2f6384a..4b580b5 100644
--- a/block.c
+++ b/block.c
@@ -1159,8 +1159,9 @@ void bdrv_set_dev_ops(BlockDriverState *bs, const BlockDevOps *ops,
     }
 }
 
-static void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
-                                      BlockErrorAction action, int is_read)
+void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
+                               enum MonitorEvent ev,
+                               BlockErrorAction action, int is_read)
 {
     QObject *data;
     const char *action_str;
@@ -1183,7 +1184,7 @@ static void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
                               bdrv->device_name,
                               action_str,
                               is_read ? "read" : "write");
-    monitor_protocol_event(QEVENT_BLOCK_IO_ERROR, data);
+    monitor_protocol_event(ev, data);
 
     qobject_decref(data);
 }
@@ -2424,7 +2425,7 @@ void bdrv_error_action(BlockDriverState *bs, BlockErrorAction action,
                        int is_read, int error)
 {
     assert(error >= 0);
-    bdrv_emit_qmp_error_event(bs, action, is_read);
+    bdrv_emit_qmp_error_event(bs, QEVENT_BLOCK_IO_ERROR, action, is_read);
     if (action == BDRV_ACTION_STOP) {
         vm_stop(RUN_STATE_IO_ERROR);
         bdrv_iostatus_set_err(bs, error);
diff --git a/block_int.h b/block_int.h
index afe3dd1..53fec71 100644
--- a/block_int.h
+++ b/block_int.h
@@ -30,6 +30,7 @@
 #include "qemu-coroutine.h"
 #include "qemu-timer.h"
 #include "qapi-types.h"
+#include "monitor.h"
 
 #define BLOCK_FLAG_ENCRYPT	1
 #define BLOCK_FLAG_COMPAT6	4
@@ -277,6 +278,9 @@ void bdrv_set_io_limits(BlockDriverState *bs,
 #ifdef _WIN32
 int is_windows_drive(const char *filename);
 #endif
+void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
+                               enum MonitorEvent ev,
+                               BlockErrorAction action, int is_read);
 
 /**
  * stream_start:
diff --git a/blockjob.c b/blockjob.c
index a18da3f..562e0b5 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -112,6 +112,7 @@ bool block_job_is_paused(BlockJob *job)
 void block_job_resume(BlockJob *job)
 {
     job->paused = false;
+    job->iostatus = BLOCK_DEVICE_IO_STATUS_OK;
     if (job->co && !job->busy) {
         qemu_coroutine_enter(job->co, NULL);
     }
@@ -189,11 +190,60 @@ void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns)
 BlockJobInfo *block_job_query(BlockJob *job)
 {
     BlockJobInfo *info = g_new(BlockJobInfo, 1);
-    info->type   = g_strdup(job->job_type->job_type);
-    info->device = g_strdup(bdrv_get_device_name(job->bs));
-    info->len    = job->len;
-    info->paused = job->paused;
-    info->offset = job->offset;
-    info->speed  = job->speed;
+    info->type      = g_strdup(job->job_type->job_type);
+    info->device    = g_strdup(bdrv_get_device_name(job->bs));
+    info->len       = job->len;
+    info->paused    = job->paused;
+    info->offset    = job->offset;
+    info->speed     = job->speed;
+    info->io_status = job->iostatus;
     return info;
 }
+
+static void block_job_iostatus_set_err(BlockJob *job, int error)
+{
+    BlockDeviceIoStatus new_status =
+        (error == ENOSPC ? BLOCK_DEVICE_IO_STATUS_NOSPACE :
+                           BLOCK_DEVICE_IO_STATUS_FAILED);
+
+    /* iostatus values are sorted from less severe to most severe
+     * (ok, nospace, failed).  */
+    if (job->iostatus < new_status) {
+        job->iostatus = new_status;
+    }
+}
+
+
+BlockErrorAction block_job_error_action(BlockJob *job, BlockDriverState *bs,
+                                        BlockdevOnError on_err,
+                                        int is_read, int error)
+{
+    BlockErrorAction action;
+
+    switch (on_err) {
+    case BLOCKDEV_ON_ERROR_ENOSPC:
+        action = (error == ENOSPC) ? BDRV_ACTION_STOP : BDRV_ACTION_REPORT;
+        break;
+    case BLOCKDEV_ON_ERROR_STOP:
+        action = BDRV_ACTION_STOP;
+        break;
+    case BLOCKDEV_ON_ERROR_REPORT:
+        action = BDRV_ACTION_REPORT;
+        break;
+    case BLOCKDEV_ON_ERROR_IGNORE:
+        action = BDRV_ACTION_IGNORE;
+        break;
+    default:
+        abort();
+    }
+    bdrv_emit_qmp_error_event(job->bs, QEVENT_BLOCK_JOB_ERROR, action, is_read);
+    if (action == BDRV_ACTION_STOP) {
+        block_job_pause(job);
+        if (bs == job->bs) {
+            block_job_iostatus_set_err(job, error);
+        } else {
+            bdrv_iostatus_set_err(bs, error);
+        }
+    }
+    return action;
+}
diff --git a/blockjob.h b/blockjob.h
index 58e1b56..8f0b93b 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -84,6 +84,9 @@ struct BlockJob {
      */
     bool busy;
 
+    /** Status that is published by the query-block-jobs QMP API */
+    BlockDeviceIoStatus iostatus;
+
     /** Offset that is published by the query-block-jobs QMP API */
     int64_t offset;
 
@@ -218,5 +221,19 @@ bool block_job_is_paused(BlockJob *job);
  */
 int block_job_cancel_sync(BlockJob *job);
 
+/**
+ * block_job_error_action:
+ * @job: The job to signal an error for.
+ * @bs: The block device on which to set an I/O error.
+ * @on_err: The error action setting.
+ * @is_read: Whether the operation was a read.
+ * @error: The error that was reported.
+ *
+ * Report an I/O error for a block job and possibly stop the VM.  Return the
+ * action that was selected based on @on_err and @error.
+ */
+BlockErrorAction block_job_error_action(BlockJob *job, BlockDriverState *bs,
+                                        BlockdevOnError on_err,
+                                        int is_read, int error);
 #endif
 
diff --git a/monitor.c b/monitor.c
index a3bc2c7..b6d67f0 100644
--- a/monitor.c
+++ b/monitor.c
@@ -440,6 +440,7 @@ static const char *monitor_event_names[] = {
     [QEVENT_SPICE_DISCONNECTED] = "SPICE_DISCONNECTED",
     [QEVENT_BLOCK_JOB_COMPLETED] = "BLOCK_JOB_COMPLETED",
     [QEVENT_BLOCK_JOB_CANCELLED] = "BLOCK_JOB_CANCELLED",
+    [QEVENT_BLOCK_JOB_ERROR] = "BLOCK_JOB_ERROR",
     [QEVENT_DEVICE_TRAY_MOVED] = "DEVICE_TRAY_MOVED",
     [QEVENT_SUSPEND] = "SUSPEND",
     [QEVENT_WAKEUP] = "WAKEUP",
diff --git a/monitor.h b/monitor.h
index cd1d878..5931636 100644
--- a/monitor.h
+++ b/monitor.h
@@ -38,6 +38,7 @@ typedef enum MonitorEvent {
     QEVENT_SPICE_DISCONNECTED,
     QEVENT_BLOCK_JOB_COMPLETED,
     QEVENT_BLOCK_JOB_CANCELLED,
+    QEVENT_BLOCK_JOB_ERROR,
     QEVENT_DEVICE_TRAY_MOVED,
     QEVENT_SUSPEND,
     QEVENT_WAKEUP,
diff --git a/qapi-schema.json b/qapi-schema.json
index d09d267..0a1a35b 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -935,11 +935,14 @@
 #
 # @speed: the rate limit, bytes per second
 #
+# @io-status: the status of the job
+#
 # Since: 1.1
 ##
 { 'type': 'BlockJobInfo',
   'data': {'type': 'str', 'device': 'str', 'len': 'int',
-           'offset': 'int', 'paused': 'bool', 'speed': 'int'} }
+           'offset': 'int', 'paused': 'bool', 'speed': 'int',
+           'io-status': 'BlockDeviceIoStatus'} }
 
 ##
 # @query-block-jobs:
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 14/36] stream: add on_error argument
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (12 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 13/36] block: introduce block job error Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 17:58   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 15/36] qemu-iotests: add tests for streaming error handling Paolo Bonzini
                   ` (21 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

This patch adds support for error management to streaming.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/stream.c   |   28 +++++++++++++++++++++++++++-
 block_int.h      |    3 ++-
 blockdev.c       |   11 ++++++++---
 hmp.c            |    3 ++-
 qapi-schema.json |    9 ++++++---
 qmp-commands.hx  |    2 +-
 6 files changed, 46 insertions(+), 10 deletions(-)

diff --git a/block/stream.c b/block/stream.c
index b3ede44..270c8d8 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -31,6 +31,7 @@ typedef struct StreamBlockJob {
     BlockJob common;
     RateLimit limit;
     BlockDriverState *base;
+    BlockdevOnError on_error;
     char backing_file_id[1024];
 } StreamBlockJob;
 
@@ -78,6 +79,7 @@ static void coroutine_fn stream_run(void *opaque)
     BlockDriverState *bs = s->common.bs;
     BlockDriverState *base = s->base;
     int64_t sector_num, end;
+    int error = 0;
     int ret = 0;
     int n = 0;
     void *buf;
@@ -136,7 +138,19 @@ wait:
             ret = stream_populate(bs, sector_num, n, buf);
         }
         if (ret < 0) {
-            break;
+            BlockErrorAction action =
+                block_job_error_action(&s->common, s->common.bs, s->on_error,
+                                       true, -ret);
+            if (action == BDRV_ACTION_STOP) {
+                n = 0;
+                continue;
+            }
+            if (error == 0) {
+                error = ret;
+            }
+            if (action == BDRV_ACTION_REPORT) {
+                break;
+            }
         }
         ret = 0;
 
@@ -148,6 +162,9 @@ wait:
         bdrv_disable_copy_on_read(bs);
     }
 
+    /* Do not remove the backing file if an error was there but ignored.  */
+    ret = error;
+
     if (!block_job_is_cancelled(&s->common) && sector_num == end && ret == 0) {
         const char *base_id = NULL, *base_fmt = NULL;
         if (base) {
@@ -183,11 +200,19 @@ static BlockJobType stream_job_type = {
 
 void stream_start(BlockDriverState *bs, BlockDriverState *base,
                   const char *base_id, int64_t speed,
+                  BlockdevOnError on_error,
                   BlockDriverCompletionFunc *cb,
                   void *opaque, Error **errp)
 {
     StreamBlockJob *s;
 
+    if ((on_error == BLOCKDEV_ON_ERROR_STOP ||
+         on_error == BLOCKDEV_ON_ERROR_ENOSPC) &&
+        !bdrv_iostatus_is_enabled(bs)) {
+        error_set(errp, QERR_INVALID_PARAMETER_COMBINATION);
+        return;
+    }
+
     s = block_job_create(&stream_job_type, bs, speed, cb, opaque, errp);
     if (!s) {
         return;
@@ -198,6 +223,7 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
         pstrcpy(s->backing_file_id, sizeof(s->backing_file_id), base_id);
     }
 
+    s->on_error = on_error;
     s->common.co = qemu_coroutine_create(stream_run);
     trace_stream_start(bs, base, s, s->common.co, opaque);
     qemu_coroutine_enter(s->common.co, s);
diff --git a/block_int.h b/block_int.h
index 53fec71..731ef1a 100644
--- a/block_int.h
+++ b/block_int.h
@@ -290,6 +290,7 @@ void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
  * @base_id: The file name that will be written to @bs as the new
  * backing file if the job completes.  Ignored if @base is %NULL.
  * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
+ * @on_error: The action to take upon error.
  * @cb: Completion function for the job.
  * @opaque: Opaque pointer value passed to @cb.
  * @errp: Error object.
@@ -301,7 +302,7 @@ void bdrv_emit_qmp_error_event(const BlockDriverState *bdrv,
  * @base_id in the written image and to @base in the live BlockDriverState.
  */
 void stream_start(BlockDriverState *bs, BlockDriverState *base,
-                  const char *base_id, int64_t speed,
+                  const char *base_id, int64_t speed, BlockdevOnError on_error,
                   BlockDriverCompletionFunc *cb,
                   void *opaque, Error **errp);
 
diff --git a/blockdev.c b/blockdev.c
index 51bc488..adb21b9 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1092,13 +1092,18 @@ static void block_stream_cb(void *opaque, int ret)
 }
 
 void qmp_block_stream(const char *device, bool has_base,
-                      const char *base, bool has_speed,
-                      int64_t speed, Error **errp)
+                      const char *base, 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;
 
+    if (!has_on_error) {
+        on_error = BLOCKDEV_ON_ERROR_REPORT;
+    }
+
     bs = bdrv_find(device);
     if (!bs) {
         error_set(errp, QERR_DEVICE_NOT_FOUND, device);
@@ -1114,7 +1119,7 @@ void qmp_block_stream(const char *device, bool has_base,
     }
 
     stream_start(bs, base_bs, base, has_speed ? speed : 0,
-                 block_stream_cb, bs, &local_err);
+                 on_error, block_stream_cb, bs, &local_err);
     if (error_is_set(&local_err)) {
         error_propagate(errp, local_err);
         return;
diff --git a/hmp.c b/hmp.c
index 9f6b255..b91fd32 100644
--- a/hmp.c
+++ b/hmp.c
@@ -843,7 +843,8 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
     int64_t speed = qdict_get_try_int(qdict, "speed", 0);
 
     qmp_block_stream(device, base != NULL, base,
-                     qdict_haskey(qdict, "speed"), speed, &error);
+                     qdict_haskey(qdict, "speed"), speed,
+                     BLOCKDEV_ON_ERROR_REPORT, true, &error);
 
     hmp_handle_error(mon, &error);
 }
diff --git a/qapi-schema.json b/qapi-schema.json
index 0a1a35b..3c99dbb 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1650,6 +1650,8 @@
 #
 # @speed:  #optional the maximum speed, in bytes per second
 #
+# @on_error: #optional the action to take on an error (default report)
+#
 # Returns: Nothing on success
 #          If streaming is already active on this device, DeviceInUse
 #          If @device does not exist, DeviceNotFound
@@ -1657,10 +1659,11 @@
 #          If @base does not exist, BaseNotFound
 #          If @speed is invalid, InvalidParameter
 #
-# Since: 1.1
+# Since: 1.1, on_error since 1.2
 ##
-{ 'command': 'block-stream', 'data': { 'device': 'str', '*base': 'str',
-                                       '*speed': 'int' } }
+{ 'command': 'block-stream',
+  'data': { 'device': 'str', '*base': 'str', '*speed': 'int',
+            '*on_error': 'BlockdevOnError' } }
 
 ##
 # @block-job-set-speed:
diff --git a/qmp-commands.hx b/qmp-commands.hx
index f20754a..e2d77b6 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -718,7 +718,7 @@ EQMP
 
     {
         .name       = "block-stream",
-        .args_type  = "device:B,base:s?,speed:o?",
+        .args_type  = "device:B,base:s?,speed:o?,on_error:s?",
         .mhandler.cmd_new = qmp_marshal_input_block_stream,
     },
 
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 15/36] qemu-iotests: add tests for streaming error handling
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (13 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 14/36] stream: add on_error argument Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 16/36] block: add bdrv_query_info Paolo Bonzini
                   ` (20 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Add a test for each of report/ignore/stop.  The tests use blkdebug
to generate an error in the middle of a script.  The error is
recoverable (once = "on") so that we can test resuming a job after
stopping for an error.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 tests/qemu-iotests/030        |  126 +++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/iotests.py |    7 +++
 2 files changed, 133 insertions(+)

diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030
index 0163945..da33943 100755
--- a/tests/qemu-iotests/030
+++ b/tests/qemu-iotests/030
@@ -163,6 +163,132 @@ class TestSingleDrive(ImageStreamingTestCase):
         result = self.vm.qmp('block-stream', device='nonexistent')
         self.assert_qmp(result, 'error/class', 'DeviceNotFound')
 
+class TestErrors(ImageStreamingTestCase):
+    image_len = 2 * 1024 * 1024 # MB
+
+    # this should match STREAM_BUFFER_SIZE/512 in block/stream.c
+    STREAM_BUFFER_SIZE = 512 * 1024
+
+    def create_blkdebug_file(self, name, event, errno):
+        file = open(name, 'w')
+        file.write('''
+[inject-error]
+event = "%s"
+errno = "%d"
+immediately = "off"
+once = "on"
+sector = "%d"
+''' % (event, errno, self.STREAM_BUFFER_SIZE / 512))
+        file.close()
+
+    def setUp(self):
+        blkdebug_file = backing_img + ".blkdebug"
+        self.create_image(backing_img, TestErrors.image_len)
+        self.create_blkdebug_file(blkdebug_file, "read_aio", 5)
+        qemu_img('create', '-f', iotests.imgfmt,
+                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
+                       % (blkdebug_file, backing_img),
+                 test_img)
+        self.vm = iotests.VM().add_drive(test_img)
+        self.vm.launch()
+
+    def tearDown(self):
+        self.vm.shutdown()
+        #os.remove(test_img)
+        #os.remove(backing_img)
+
+    def test_report(self):
+        self.assert_no_active_streams()
+
+        result = self.vm.qmp('block-stream', device='drive0')
+        self.assert_qmp(result, 'return', {})
+
+        completed = False
+        error = False
+        while not completed:
+            for event in self.vm.get_qmp_events(wait=True):
+                if event['event'] == 'BLOCK_JOB_ERROR':
+                    self.assert_qmp(event, 'data/device', 'drive0')
+                    self.assert_qmp(event, 'data/operation', 'read')
+                    error = True
+                elif event['event'] == 'BLOCK_JOB_COMPLETED':
+                    self.assertTrue(error, 'job completed unexpectedly')
+                    self.assert_qmp(event, 'data/type', 'stream')
+                    self.assert_qmp(event, 'data/device', 'drive0')
+                    self.assert_qmp(event, 'data/error', 'Input/output error')
+                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
+                    self.assert_qmp(event, 'data/len', self.image_len)
+                    completed = True
+
+        self.assert_no_active_streams()
+        self.vm.shutdown()
+
+    def test_ignore(self):
+        self.assert_no_active_streams()
+
+        result = self.vm.qmp('block-stream', device='drive0', on_error='ignore')
+        self.assert_qmp(result, 'return', {})
+
+        error = False
+        completed = False
+        while not completed:
+            for event in self.vm.get_qmp_events(wait=True):
+                if event['event'] == 'BLOCK_JOB_ERROR':
+                    self.assert_qmp(event, 'data/device', 'drive0')
+                    self.assert_qmp(event, 'data/operation', 'read')
+                    result = self.vm.qmp('query-block-jobs')
+                    self.assert_qmp(result, 'return[0]/paused', False)
+                    error = True
+                elif event['event'] == 'BLOCK_JOB_COMPLETED':
+                    self.assertTrue(error, 'job completed unexpectedly')
+                    self.assert_qmp(event, 'data/type', 'stream')
+                    self.assert_qmp(event, 'data/device', 'drive0')
+                    self.assert_qmp(event, 'data/error', 'Input/output error')
+                    self.assert_qmp(event, 'data/offset', self.image_len)
+                    self.assert_qmp(event, 'data/len', self.image_len)
+                    completed = True
+
+        self.assert_no_active_streams()
+        self.vm.shutdown()
+
+    def test_stop(self):
+        self.assert_no_active_streams()
+
+        result = self.vm.qmp('block-stream', device='drive0', on_error='stop')
+        self.assert_qmp(result, 'return', {})
+
+        error = False
+        completed = False
+        while not completed:
+            for event in self.vm.get_qmp_events(wait=True):
+                if event['event'] == 'BLOCK_JOB_ERROR':
+                    self.assert_qmp(event, 'data/device', 'drive0')
+                    self.assert_qmp(event, 'data/operation', 'read')
+
+                    result = self.vm.qmp('query-block-jobs')
+                    self.assert_qmp(result, 'return[0]/paused', True)
+                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
+                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
+
+                    result = self.vm.qmp('block-job-resume', device='drive0')
+                    self.assert_qmp(result, 'return', {})
+
+                    result = self.vm.qmp('query-block-jobs')
+                    self.assert_qmp(result, 'return[0]/paused', False)
+                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
+                    error = True
+                elif event['event'] == 'BLOCK_JOB_COMPLETED':
+                    self.assertTrue(error, 'job completed unexpectedly')
+                    self.assert_qmp(event, 'data/type', 'stream')
+                    self.assert_qmp(event, 'data/device', 'drive0')
+                    self.assert_qmp_absent(event, 'data/error')
+                    self.assert_qmp(event, 'data/offset', self.image_len)
+                    self.assert_qmp(event, 'data/len', self.image_len)
+                    completed = True
+
+        self.assert_no_active_streams()
+        self.vm.shutdown()
+
 class TestStreamStop(ImageStreamingTestCase):
     image_len = 8 * 1024 * 1024 * 1024 # GB
 
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index e05b1d6..805c0e8 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -132,6 +132,13 @@ class QMPTestCase(unittest.TestCase):
                     self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
         return d
 
+    def assert_qmp_absent(self, d, path):
+        try:
+            result = self.dictpath(d, path)
+        except AssertionError:
+            return
+        self.fail('path "%s" has value "%s"' % (path, str(result)))
+
     def assert_qmp(self, d, path, value):
         '''Assert that the value for a specific path in a QMP dict matches'''
         result = self.dictpath(d, path)
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 16/36] block: add bdrv_query_info
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (14 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 15/36] qemu-iotests: add tests for streaming error handling Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 17/36] block: add bdrv_query_stats Paolo Bonzini
                   ` (19 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Extract it out of the implementation of "info block".

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c |  104 +++++++++++++++++++++++++++++++--------------------------------
 block.h |    1 +
 2 files changed, 53 insertions(+), 52 deletions(-)

diff --git a/block.c b/block.c
index 4b580b5..61cbd2d 100644
--- a/block.c
+++ b/block.c
@@ -2702,63 +2702,15 @@ int coroutine_fn bdrv_co_is_allocated_above(BlockDriverState *top,
 
 BlockInfoList *qmp_query_block(Error **errp)
 {
-    BlockInfoList *head = NULL, *cur_item = NULL;
+    BlockInfoList *head = NULL, **p_next = &head;
     BlockDriverState *bs;
 
     QTAILQ_FOREACH(bs, &bdrv_states, list) {
         BlockInfoList *info = g_malloc0(sizeof(*info));
+        info->value = bdrv_query_info(bs);
 
-        info->value = g_malloc0(sizeof(*info->value));
-        info->value->device = g_strdup(bs->device_name);
-        info->value->type = g_strdup("unknown");
-        info->value->locked = bdrv_dev_is_medium_locked(bs);
-        info->value->removable = bdrv_dev_has_removable_media(bs);
-
-        if (bdrv_dev_has_removable_media(bs)) {
-            info->value->has_tray_open = true;
-            info->value->tray_open = bdrv_dev_is_tray_open(bs);
-        }
-
-        if (bdrv_iostatus_is_enabled(bs)) {
-            info->value->has_io_status = true;
-            info->value->io_status = bs->iostatus;
-        }
-
-        if (bs->drv) {
-            info->value->has_inserted = true;
-            info->value->inserted = g_malloc0(sizeof(*info->value->inserted));
-            info->value->inserted->file = g_strdup(bs->filename);
-            info->value->inserted->ro = bs->read_only;
-            info->value->inserted->drv = g_strdup(bs->drv->format_name);
-            info->value->inserted->encrypted = bs->encrypted;
-            if (bs->backing_file[0]) {
-                info->value->inserted->has_backing_file = true;
-                info->value->inserted->backing_file = g_strdup(bs->backing_file);
-            }
-
-            if (bs->io_limits_enabled) {
-                info->value->inserted->bps =
-                               bs->io_limits.bps[BLOCK_IO_LIMIT_TOTAL];
-                info->value->inserted->bps_rd =
-                               bs->io_limits.bps[BLOCK_IO_LIMIT_READ];
-                info->value->inserted->bps_wr =
-                               bs->io_limits.bps[BLOCK_IO_LIMIT_WRITE];
-                info->value->inserted->iops =
-                               bs->io_limits.iops[BLOCK_IO_LIMIT_TOTAL];
-                info->value->inserted->iops_rd =
-                               bs->io_limits.iops[BLOCK_IO_LIMIT_READ];
-                info->value->inserted->iops_wr =
-                               bs->io_limits.iops[BLOCK_IO_LIMIT_WRITE];
-            }
-        }
-
-        /* XXX: waiting for the qapi to support GSList */
-        if (!cur_item) {
-            head = cur_item = info;
-        } else {
-            cur_item->next = info;
-            cur_item = info;
-        }
+        *p_next = info;
+        p_next = &info->next;
     }
 
     return head;
@@ -2795,6 +2747,54 @@ static BlockStats *qmp_query_blockstat(const BlockDriverState *bs, Error **errp)
     return s;
 }
 
+BlockInfo *bdrv_query_info(BlockDriverState *bs)
+{
+    BlockInfo *info = g_malloc0(sizeof(*info));
+    info->device = g_strdup(bs->device_name);
+    info->type = g_strdup("unknown");
+    info->locked = bdrv_dev_is_medium_locked(bs);
+    info->removable = bdrv_dev_has_removable_media(bs);
+
+    if (bdrv_dev_has_removable_media(bs)) {
+        info->has_tray_open = true;
+        info->tray_open = bdrv_dev_is_tray_open(bs);
+    }
+
+    if (bdrv_iostatus_is_enabled(bs)) {
+        info->has_io_status = true;
+        info->io_status = bs->iostatus;
+    }
+
+    if (bs->drv) {
+        info->has_inserted = true;
+        info->inserted = g_malloc0(sizeof(*info->inserted));
+        info->inserted->file = g_strdup(bs->filename);
+        info->inserted->ro = bs->read_only;
+        info->inserted->drv = g_strdup(bs->drv->format_name);
+        info->inserted->encrypted = bs->encrypted;
+        if (bs->backing_file[0]) {
+            info->inserted->has_backing_file = true;
+            info->inserted->backing_file = g_strdup(bs->backing_file);
+        }
+
+        if (bs->io_limits_enabled) {
+            info->inserted->bps =
+                           bs->io_limits.bps[BLOCK_IO_LIMIT_TOTAL];
+            info->inserted->bps_rd =
+                           bs->io_limits.bps[BLOCK_IO_LIMIT_READ];
+            info->inserted->bps_wr =
+                           bs->io_limits.bps[BLOCK_IO_LIMIT_WRITE];
+            info->inserted->iops =
+                           bs->io_limits.iops[BLOCK_IO_LIMIT_TOTAL];
+            info->inserted->iops_rd =
+                           bs->io_limits.iops[BLOCK_IO_LIMIT_READ];
+            info->inserted->iops_wr =
+                           bs->io_limits.iops[BLOCK_IO_LIMIT_WRITE];
+        }
+    }
+    return info;
+}
+
 BlockStatsList *qmp_query_blockstats(Error **errp)
 {
     BlockStatsList *head = NULL, *cur_item = NULL;
diff --git a/block.h b/block.h
index 32b075c..265d544 100644
--- a/block.h
+++ b/block.h
@@ -317,6 +317,7 @@ void bdrv_get_backing_filename(BlockDriverState *bs,
                                char *filename, int filename_size);
 void bdrv_get_full_backing_filename(BlockDriverState *bs,
                                     char *dest, size_t sz);
+BlockInfo *bdrv_query_info(BlockDriverState *s);
 int bdrv_can_snapshot(BlockDriverState *bs);
 int bdrv_is_snapshot(BlockDriverState *bs);
 BlockDriverState *bdrv_snapshots(void);
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 17/36] block: add bdrv_query_stats
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (15 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 16/36] block: add bdrv_query_info Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 18/36] block: make device optional in BlockInfo Paolo Bonzini
                   ` (18 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Extract it out of the implementation of "info blockstats".

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c |   17 ++++++-----------
 block.h |    1 +
 2 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/block.c b/block.c
index 61cbd2d..5d293a0 100644
--- a/block.c
+++ b/block.c
@@ -2717,7 +2717,7 @@ BlockInfoList *qmp_query_block(Error **errp)
 }
 
 /* Consider exposing this as a full fledged QMP command */
-static BlockStats *qmp_query_blockstat(const BlockDriverState *bs, Error **errp)
+BlockStats *bdrv_query_stats(const BlockDriverState *bs)
 {
     BlockStats *s;
 
@@ -2741,7 +2741,7 @@ static BlockStats *qmp_query_blockstat(const BlockDriverState *bs, Error **errp)
 
     if (bs->file) {
         s->has_parent = true;
-        s->parent = qmp_query_blockstat(bs->file, NULL);
+        s->parent = bdrv_query_stats(bs->file);
     }
 
     return s;
@@ -2797,20 +2797,15 @@ BlockInfo *bdrv_query_info(BlockDriverState *bs)
 
 BlockStatsList *qmp_query_blockstats(Error **errp)
 {
-    BlockStatsList *head = NULL, *cur_item = NULL;
+    BlockStatsList *head = NULL, **p_next = &head;
     BlockDriverState *bs;
 
     QTAILQ_FOREACH(bs, &bdrv_states, list) {
         BlockStatsList *info = g_malloc0(sizeof(*info));
-        info->value = qmp_query_blockstat(bs, NULL);
+        info->value = bdrv_query_stats(bs);
 
-        /* XXX: waiting for the qapi to support GSList */
-        if (!cur_item) {
-            head = cur_item = info;
-        } else {
-            cur_item->next = info;
-            cur_item = info;
-        }
+        *p_next = info;
+        p_next = &info->next;
     }
 
     return head;
diff --git a/block.h b/block.h
index 265d544..1e13469 100644
--- a/block.h
+++ b/block.h
@@ -318,6 +318,7 @@ void bdrv_get_backing_filename(BlockDriverState *bs,
 void bdrv_get_full_backing_filename(BlockDriverState *bs,
                                     char *dest, size_t sz);
 BlockInfo *bdrv_query_info(BlockDriverState *s);
+BlockStats *bdrv_query_stats(const BlockDriverState *bs);
 int bdrv_can_snapshot(BlockDriverState *bs);
 int bdrv_is_snapshot(BlockDriverState *bs);
 BlockDriverState *bdrv_snapshots(void);
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 18/36] block: make device optional in BlockInfo
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (16 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 17/36] block: add bdrv_query_stats Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 19/36] block: add target info to QMP query-blockjobs command Paolo Bonzini
                   ` (17 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Targets of a mirroring operation will not have a device.  Once we have
-blockdev or equivalent, "detached" block devices and non-anonymous
backing files also will not have a device.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 qapi-schema.json |    5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index 3c99dbb..93205af 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -437,7 +437,8 @@
 # Block device information.  This structure describes a virtual device and
 # the backing device associated with it.
 #
-# @device: The device name associated with the virtual device.
+# @device: #optional The device name associated with the virtual device.
+#          Always included in the output of query-block.
 #
 # @type: This field is returned only for compatibility reasons, it should
 #        not be used (always returns 'unknown')
@@ -459,7 +460,7 @@
 # Since:  0.14.0
 ##
 { 'type': 'BlockInfo',
-  'data': {'device': 'str', 'type': 'str', 'removable': 'bool',
+  'data': {'*device': 'str', 'type': 'str', 'removable': 'bool',
            'locked': 'bool', '*inserted': 'BlockDeviceInfo',
            '*tray_open': 'bool', '*io-status': 'BlockDeviceIoStatus'} }
 
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 19/36] block: add target info to QMP query-blockjobs command
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (17 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 18/36] block: make device optional in BlockInfo Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 20/36] block: forward bdrv_iostatus_reset to block job Paolo Bonzini
                   ` (16 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockjob.c       |    3 +++
 blockjob.h       |    6 ++++++
 qapi-schema.json |   21 ++++++++++++++++++++-
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/blockjob.c b/blockjob.c
index 562e0b5..651ee8d 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -197,6 +197,9 @@ BlockJobInfo *block_job_query(BlockJob *job)
     info->offset    = job->offset;
     info->speed     = job->speed;
     info->io_status = job->iostatus;
+    if (job->job_type->query) {
+        job->job_type->query(job, info);
+    }
     return info;
 }
 
diff --git a/blockjob.h b/blockjob.h
index 8f0b93b..6dec489 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -43,6 +43,12 @@ typedef struct BlockJobType {
 
     /** Optional callback for job types that support setting a speed limit */
     void (*set_speed)(BlockJob *job, int64_t speed, Error **errp);
+
+    /**
+     * Optional callback for job types that can fill the target member
+     * of BlockJobInfo.
+     */
+    void (*query)(BlockJob *job, BlockJobInfo *info);
 } BlockJobType;
 
 /**
diff --git a/qapi-schema.json b/qapi-schema.json
index 93205af..4524c54 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -920,6 +920,21 @@
   'data': ['report', 'ignore', 'enospc', 'stop'] }
 
 ##
+# @BlockJobTargetInfo:
+#
+# Information about the target device for a long-running block device
+# operation.
+#
+# @info: information on the target device
+#
+# @stats: statistics about a target device
+#
+# Since: 1.2
+##
+{ 'type': 'BlockJobTargetInfo',
+  'data': {'info': 'BlockInfo', 'stats': 'BlockStats'} }
+
+##
 # @BlockJobInfo:
 #
 # Information about a long-running block device operation.
@@ -938,12 +953,16 @@
 #
 # @io-status: the status of the job
 #
+# @target: the target device, if applicable to this particular type of
+#          job.
+#
 # Since: 1.1
 ##
 { 'type': 'BlockJobInfo',
   'data': {'type': 'str', 'device': 'str', 'len': 'int',
            'offset': 'int', 'paused': 'bool', 'speed': 'int',
-           'io-status': 'BlockDeviceIoStatus'} }
+           'io-status': 'BlockDeviceIoStatus',
+            '*target': 'BlockJobTargetInfo'} }
 
 ##
 # @query-block-jobs:
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 20/36] block: forward bdrv_iostatus_reset to block job
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (18 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 19/36] block: add target info to QMP query-blockjobs command Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 21/36] block: introduce new dirty bitmap functionality Paolo Bonzini
                   ` (15 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c    |    3 +++
 blockjob.c |    7 +++++++
 blockjob.h |   12 ++++++++++++
 3 files changed, 22 insertions(+)

diff --git a/block.c b/block.c
index 5d293a0..f1afdd6 100644
--- a/block.c
+++ b/block.c
@@ -4123,6 +4123,9 @@ void bdrv_iostatus_reset(BlockDriverState *bs)
 {
     if (bdrv_iostatus_is_enabled(bs)) {
         bs->iostatus = BLOCK_DEVICE_IO_STATUS_OK;
+        if (bs->job) {
+            block_job_iostatus_reset(bs->job);
+        }
     }
 }
 
diff --git a/blockjob.c b/blockjob.c
index 651ee8d..d3819df 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -129,6 +129,13 @@ bool block_job_is_cancelled(BlockJob *job)
     return job->cancelled;
 }
 
+void block_job_iostatus_reset(BlockJob *job)
+{
+    if (job->job_type->iostatus_reset) {
+        job->job_type->iostatus_reset(job);
+    }
+}
+
 struct BlockCancelData {
     BlockJob *job;
     BlockDriverCompletionFunc *cb;
diff --git a/blockjob.h b/blockjob.h
index 6dec489..02e0363 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -44,6 +44,9 @@ typedef struct BlockJobType {
     /** Optional callback for job types that support setting a speed limit */
     void (*set_speed)(BlockJob *job, int64_t speed, Error **errp);
 
+    /** Optional callback for job types that need to forward I/O status reset */
+    void (*iostatus_reset)(BlockJob *job);
+
     /**
      * Optional callback for job types that can fill the target member
      * of BlockJobInfo.
@@ -228,6 +231,15 @@ bool block_job_is_paused(BlockJob *job);
 int block_job_cancel_sync(BlockJob *job);
 
 /**
+ * block_job_iostatus_reset:
+ * @job: The job whose I/O status should be reset.
+ *
+ * Reset I/O status on BlockDriverState objects used by @job, other
+ * than job->bs.
+ */
+void block_job_iostatus_reset(BlockJob *job);
+
+/**
  * block_job_error_action:
  * @job: The job to signal an error for.
  * @bs: The block device on which to set an I/O error.
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 21/36] block: introduce new dirty bitmap functionality
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (19 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 20/36] block: forward bdrv_iostatus_reset to block job Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 22/36] block: add mirror job Paolo Bonzini
                   ` (14 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Assert that write_compressed is never used with the dirty bitmap.
Setting the bits early is wrong, because a coroutine might concurrently
examine them and copy incomplete data from the source.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c |   51 +++++++++++++++++++++++++++++++++++++++++++++------
 block.h |    5 +++--
 2 files changed, 48 insertions(+), 8 deletions(-)

diff --git a/block.c b/block.c
index f1afdd6..dbb9041 100644
--- a/block.c
+++ b/block.c
@@ -2021,7 +2021,7 @@ static int coroutine_fn bdrv_co_do_writev(BlockDriverState *bs,
     }
 
     if (bs->dirty_bitmap) {
-        set_dirty_bitmap(bs, sector_num, nb_sectors, 1);
+        bdrv_set_dirty(bs, sector_num, nb_sectors);
     }
 
     if (bs->wr_highest_sector < sector_num + nb_sectors - 1) {
@@ -2838,9 +2838,7 @@ int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num,
     if (bdrv_check_request(bs, sector_num, nb_sectors))
         return -EIO;
 
-    if (bs->dirty_bitmap) {
-        set_dirty_bitmap(bs, sector_num, nb_sectors, 1);
-    }
+    assert(!bs->dirty_bitmap);
 
     return drv->bdrv_write_compressed(bs, sector_num, buf, nb_sectors);
 }
@@ -4069,13 +4067,54 @@ int bdrv_get_dirty(BlockDriverState *bs, int64_t sector)
 
     if (bs->dirty_bitmap &&
         (sector << BDRV_SECTOR_BITS) < bdrv_getlength(bs)) {
-        return !!(bs->dirty_bitmap[chunk / (sizeof(unsigned long) * 8)] &
-            (1UL << (chunk % (sizeof(unsigned long) * 8))));
+        return !!(bs->dirty_bitmap[chunk / BITS_PER_LONG] &
+            (1UL << (chunk % BITS_PER_LONG)));
     } else {
         return 0;
     }
 }
 
+int64_t bdrv_get_next_dirty(BlockDriverState *bs, int64_t sector)
+{
+    int64_t chunk;
+    int bit, elem;
+
+    /* Avoid an infinite loop.  */
+    assert(bs->dirty_count > 0);
+
+    sector = (sector | (BDRV_SECTORS_PER_DIRTY_CHUNK - 1)) + 1;
+    chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK;
+
+    QEMU_BUILD_BUG_ON(sizeof(bs->dirty_bitmap[0]) * 8 != BITS_PER_LONG);
+    elem = chunk / BITS_PER_LONG;
+    bit = chunk % BITS_PER_LONG;
+    for (;;) {
+        if (sector >= bs->total_sectors) {
+            sector = 0;
+            bit = elem = 0;
+        }
+        if (bit == 0 && bs->dirty_bitmap[elem] == 0) {
+            sector += BDRV_SECTORS_PER_DIRTY_CHUNK * BITS_PER_LONG;
+            elem++;
+        } else {
+            if (bs->dirty_bitmap[elem] & (1UL << bit)) {
+                return sector;
+            }
+            sector += BDRV_SECTORS_PER_DIRTY_CHUNK;
+            if (++bit == BITS_PER_LONG) {
+                bit = 0;
+                elem++;
+            }
+        }
+    }
+}
+
+void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
+                    int nr_sectors)
+{
+    set_dirty_bitmap(bs, cur_sector, nr_sectors, 1);
+}
+
 void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
                       int nr_sectors)
 {
diff --git a/block.h b/block.h
index 1e13469..c13d779 100644
--- a/block.h
+++ b/block.h
@@ -356,8 +356,9 @@ void *qemu_blockalign(BlockDriverState *bs, size_t size);
 
 void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable);
 int bdrv_get_dirty(BlockDriverState *bs, int64_t sector);
-void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
-                      int nr_sectors);
+void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
+void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
+int64_t bdrv_get_next_dirty(BlockDriverState *bs, int64_t sector);
 int64_t bdrv_get_dirty_count(BlockDriverState *bs);
 
 void bdrv_enable_copy_on_read(BlockDriverState *bs);
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 22/36] block: add mirror job
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (20 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 21/36] block: introduce new dirty bitmap functionality Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 18:20   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 23/36] qmp: add drive-mirror command Paolo Bonzini
                   ` (13 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

This patch adds the implementation of a new job that mirrors a disk to
a new image while letting the guest continue using the old image.
The target is treated as a "black box" and data is copied from the
source to the target in the background.

The mirror job is never-ending, but it is logically structured into
two phases: 1) copy all data as fast as possible until the target
first gets in sync with the source; 2) keep target in sync and
ensure that reopening to the target gets a correct (full) copy
of the source data.

The second phase is indicated by the progress in "info block-jobs"
reporting the current offset to be equal to the length of the file.
When the job is cancelled in the second phase, QEMU will run the
job until the source is clean and quiescent, then it will report
successful completion of the job.  (Note that it could already happen
that management lost the race against QEMU and got a completion
event instead of cancellation).

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/Makefile.objs |    2 +-
 block/mirror.c      |  228 +++++++++++++++++++++++++++++++++++++++++++++++++++
 block_int.h         |   20 +++++
 qapi-schema.json    |   17 ++++
 trace-events        |    4 +
 5 files changed, 270 insertions(+), 1 deletion(-)
 create mode 100644 block/mirror.c

diff --git a/block/Makefile.objs b/block/Makefile.objs
index c45affc..f1a394a 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -9,4 +9,4 @@ block-obj-$(CONFIG_LIBISCSI) += iscsi.o
 block-obj-$(CONFIG_CURL) += curl.o
 block-obj-$(CONFIG_RBD) += rbd.o
 
-common-obj-y += stream.o
+common-obj-y += stream.o mirror.o
diff --git a/block/mirror.c b/block/mirror.c
new file mode 100644
index 0000000..1ed19a9
--- /dev/null
+++ b/block/mirror.c
@@ -0,0 +1,228 @@
+/*
+ * Image mirroring
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Authors:
+ *  Paolo Bonzini  <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include "trace.h"
+#include "blockjob.h"
+#include "block_int.h"
+#include "qemu/ratelimit.h"
+
+enum {
+    /*
+     * Size of data buffer for populating the image file.  This should be large
+     * enough to process multiple clusters in a single call, so that populating
+     * contiguous regions of the image is efficient.
+     */
+    BLOCK_SIZE = 512 * BDRV_SECTORS_PER_DIRTY_CHUNK, /* in bytes */
+};
+
+#define SLICE_TIME 100000000ULL /* ns */
+
+typedef struct MirrorBlockJob {
+    BlockJob common;
+    RateLimit limit;
+    BlockDriverState *target;
+    MirrorSyncMode mode;
+    int64_t sector_num;
+    void *buf;
+} MirrorBlockJob;
+
+static int coroutine_fn mirror_iteration(MirrorBlockJob *s)
+{
+    BlockDriverState *source = s->common.bs;
+    BlockDriverState *target = s->target;
+    QEMUIOVector qiov;
+    int ret, nb_sectors;
+    int64_t end;
+    struct iovec iov;
+
+    end = s->common.len >> BDRV_SECTOR_BITS;
+    s->sector_num = bdrv_get_next_dirty(source, s->sector_num);
+    nb_sectors = MIN(BDRV_SECTORS_PER_DIRTY_CHUNK, end - s->sector_num);
+    trace_mirror_one_iteration(s, s->sector_num);
+    bdrv_reset_dirty(source, s->sector_num, BDRV_SECTORS_PER_DIRTY_CHUNK);
+
+    /* Copy the dirty cluster.  */
+    iov.iov_base = s->buf;
+    iov.iov_len  = nb_sectors * 512;
+    qemu_iovec_init_external(&qiov, &iov, 1);
+
+    ret = bdrv_co_readv(source, s->sector_num, nb_sectors, &qiov);
+    if (ret < 0) {
+        return ret;
+    }
+    return bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov);
+}
+
+static void coroutine_fn mirror_run(void *opaque)
+{
+    MirrorBlockJob *s = opaque;
+    BlockDriverState *bs = s->common.bs;
+    int64_t sector_num, end;
+    int ret = 0;
+    int n;
+    bool synced = false;
+
+    if (block_job_is_cancelled(&s->common)) {
+        goto immediate_exit;
+    }
+
+    s->common.len = bdrv_getlength(bs);
+    if (s->common.len < 0) {
+        block_job_completed(&s->common, s->common.len);
+        return;
+    }
+
+    end = s->common.len >> BDRV_SECTOR_BITS;
+    s->buf = qemu_blockalign(bs, BLOCK_SIZE);
+
+    if (s->mode == MIRROR_SYNC_MODE_FULL || s->mode == MIRROR_SYNC_MODE_TOP) {
+        /* First part, loop on the sectors and initialize the dirty bitmap.  */
+        BlockDriverState *base;
+        base = s->mode == MIRROR_SYNC_MODE_FULL ? NULL : bs->backing_hd;
+        for (sector_num = 0; sector_num < end; ) {
+            int64_t next = (sector_num | (BDRV_SECTORS_PER_DIRTY_CHUNK - 1)) + 1;
+            ret = bdrv_co_is_allocated_above(bs, base,
+                                             sector_num, next - sector_num, &n);
+
+            if (ret < 0) {
+                break;
+            } else if (ret == 1) {
+                bdrv_set_dirty(bs, sector_num, n);
+                sector_num = next;
+            } else {
+                sector_num += n;
+            }
+        }
+    }
+
+    if (ret < 0) {
+        block_job_completed(&s->common, ret);
+    }
+
+    s->sector_num = -1;
+    for (;;) {
+        uint64_t delay_ns;
+        int64_t cnt;
+        bool should_complete;
+
+        if (bdrv_get_dirty_count(bs) != 0) {
+            ret = mirror_iteration(s);
+            if (ret < 0) {
+                break;
+            }
+        }
+
+        if (bdrv_get_dirty_count(bs) == 0) {
+            /* We're out of the streaming phase.  From now on, if the
+             * job is cancelled we will actually complete all pending
+             * I/O and report completion, so that drive-reopen can be
+             * used to pivot to the mirroring target.
+             */
+            synced = true;
+            s->common.offset = end * BDRV_SECTOR_SIZE;
+        }
+
+        should_complete = synced && block_job_is_cancelled(&s->common);
+        if (should_complete) {
+            /* The dirty bitmap is not updated while operations are pending.
+             * If we're about to exit, wait for pending operations before
+             * calling bdrv_get_dirty_count(bs), or we may exit while the
+             * source has dirty data to copy!
+             *
+             * Note that I/O can be submitted by the guest while
+             * mirror_populate runs.
+             */
+            bdrv_drain_all();
+        }
+
+        ret = 0;
+        cnt = bdrv_get_dirty_count(bs);
+        if (synced) {
+            if (!should_complete) {
+                delay_ns = (cnt == 0 ? SLICE_TIME : 0);
+                block_job_sleep_ns(&s->common, rt_clock, delay_ns);
+                continue;
+            }
+
+            if (cnt == 0) {
+                /* The two disks are in sync.  Exit and report successful
+                 * completion.
+                 */
+                assert(QLIST_EMPTY(&bs->tracked_requests));
+                s->common.cancelled = false;
+                break;
+            }
+        } else {
+            /* Publish progress */
+            s->common.offset = end * BDRV_SECTOR_SIZE - cnt * BLOCK_SIZE;
+
+            if (s->common.speed) {
+                delay_ns = ratelimit_calculate_delay(&s->limit, BDRV_SECTORS_PER_DIRTY_CHUNK);
+            } else {
+                delay_ns = 0;
+            }
+
+            /* Note that even when no rate limit is applied we need to yield
+             * with no pending I/O here so that qemu_aio_flush() returns.
+             */
+            block_job_sleep_ns(&s->common, rt_clock, delay_ns);
+            if (block_job_is_cancelled(&s->common)) {
+                break;
+            }
+        }
+    }
+
+immediate_exit:
+    g_free(s->buf);
+    bdrv_set_dirty_tracking(bs, false);
+    bdrv_close(s->target);
+    bdrv_delete(s->target);
+    block_job_completed(&s->common, ret);
+}
+
+static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
+{
+    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+
+    if (speed < 0) {
+        error_set(errp, QERR_INVALID_PARAMETER, "speed");
+        return;
+    }
+    ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
+}
+
+static BlockJobType mirror_job_type = {
+    .instance_size = sizeof(MirrorBlockJob),
+    .job_type      = "mirror",
+    .set_speed     = mirror_set_speed,
+};
+
+void mirror_start(BlockDriverState *bs, BlockDriverState *target,
+                  int64_t speed, MirrorSyncMode mode,
+                  BlockDriverCompletionFunc *cb,
+                  void *opaque, Error **errp)
+{
+    MirrorBlockJob *s;
+
+    s = block_job_create(&mirror_job_type, bs, speed, cb, opaque, errp);
+    if (!s) {
+        return;
+    }
+
+    s->target = target;
+    s->mode = mode;
+    bdrv_set_dirty_tracking(bs, true);
+    s->common.co = qemu_coroutine_create(mirror_run);
+    trace_mirror_start(bs, s, s->common.co, opaque);
+    qemu_coroutine_enter(s->common.co, s);
+}
diff --git a/block_int.h b/block_int.h
index 731ef1a..d7e1928 100644
--- a/block_int.h
+++ b/block_int.h
@@ -306,4 +306,24 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
                   BlockDriverCompletionFunc *cb,
                   void *opaque, Error **errp);
 
+/**
+ * mirror_start:
+ * @bs: Block device to operate on.
+ * @target: Block device to write to.
+ * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
+ * @mode: Whether to collapse all images in the chain to the target.
+ * @cb: Completion function for the job.
+ * @opaque: Opaque pointer value passed to @cb.
+ * @errp: Error object.
+ *
+ * Start a mirroring operation on @bs.  Clusters that are allocated
+ * in @bs will be written to @bs until the job is canceled or
+ * manually completed.  At the end of a successful mirroring job,
+ * @bs will be switched to read from @target.
+ */
+void mirror_start(BlockDriverState *bs, BlockDriverState *target,
+                  int64_t speed, MirrorSyncMode mode,
+                  BlockDriverCompletionFunc *cb,
+                  void *opaque, Error **errp);
+
 #endif /* BLOCK_INT_H */
diff --git a/qapi-schema.json b/qapi-schema.json
index 4524c54..0d54c56 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -920,6 +920,23 @@
   'data': ['report', 'ignore', 'enospc', 'stop'] }
 
 ##
+# @MirrorSyncMode:
+#
+# An enumeration of possible behaviors for the initial synchronization
+# phase of storage mirroring.
+#
+# @top: copies data in the topmost image to the destination
+#
+# @full: copies data from all images to the destination
+#
+# @none: only copy data written from now on
+#
+# Since: 1.2
+##
+{ 'enum': 'MirrorSyncMode',
+  'data': ['top', 'full', 'none'] }
+
+##
 # @BlockJobTargetInfo:
 #
 # Information about the target device for a long-running block device
diff --git a/trace-events b/trace-events
index 25ebfe9..2124105 100644
--- a/trace-events
+++ b/trace-events
@@ -75,6 +75,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"
 
+# block/mirror.c
+mirror_start(void *bs, void *s, void *co, void *opaque) "bs %p s %p co %p opaque %p"
+mirror_one_iteration(void *s, int64_t sector_num) "s %p sector_num %"PRId64
+
 # blockdev.c
 qmp_block_job_cancel(void *job) "job %p"
 qmp_block_job_pause(void *job) "job %p"
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 23/36] qmp: add drive-mirror command
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (21 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 22/36] block: add mirror job Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 20:12   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 24/36] mirror: support querying target file Paolo Bonzini
                   ` (12 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

This adds the monitor commands that start the mirroring job.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockdev.c       |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 hmp-commands.hx  |   21 ++++++++++
 hmp.c            |   28 +++++++++++++
 hmp.h            |    1 +
 qapi-schema.json |   33 +++++++++++++++
 qmp-commands.hx  |   41 +++++++++++++++++++
 trace-events     |    2 +-
 7 files changed, 242 insertions(+), 4 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index adb21b9..29ecadc 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -21,6 +21,8 @@
 #include "trace.h"
 #include "arch_init.h"
 
+static void block_job_cb(void *opaque, int ret);
+
 static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
 
 static const char *const if_name[IF_COUNT] = {
@@ -829,6 +831,118 @@ exit:
     return;
 }
 
+void qmp_drive_mirror(const char *device, const char *target,
+                      bool has_format, const char *format,
+                      enum MirrorSyncMode sync,
+                      bool has_mode, enum NewImageMode mode,
+                      bool has_speed, int64_t speed, Error **errp)
+{
+    BlockDriverState *bs;
+    BlockDriverState *source, *target_bs;
+    BlockDriver *proto_drv;
+    BlockDriver *drv = NULL;
+    Error *local_err = NULL;
+    int flags;
+    uint64_t size;
+    int ret;
+
+    if (!has_mode) {
+        mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
+    }
+
+    bs = bdrv_find(device);
+    if (!bs) {
+        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
+        return;
+    }
+
+    if (!has_format) {
+        format = mode == NEW_IMAGE_MODE_EXISTING ? NULL : bs->drv->format_name;
+    }
+    if (format) {
+        drv = bdrv_find_format(format);
+        if (!drv) {
+            error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
+            return;
+        }
+    }
+
+    if (!bdrv_is_inserted(bs)) {
+        error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
+        return;
+    }
+
+    if (bdrv_in_use(bs)) {
+        error_set(errp, QERR_DEVICE_IN_USE, device);
+        return;
+    }
+
+    flags = bs->open_flags | BDRV_O_RDWR;
+    source = bs->backing_hd;
+    if (!source && sync == MIRROR_SYNC_MODE_TOP) {
+        sync = MIRROR_SYNC_MODE_FULL;
+    }
+
+    proto_drv = bdrv_find_protocol(target);
+    if (!proto_drv) {
+        error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
+        return;
+    }
+
+    if (sync == MIRROR_SYNC_MODE_FULL && mode != NEW_IMAGE_MODE_EXISTING) {
+        /* create new image w/o backing file */
+        assert(format && drv);
+        bdrv_get_geometry(bs, &size);
+        size *= 512;
+        ret = bdrv_img_create(target, format,
+                              NULL, NULL, NULL, size, flags);
+    } else {
+        switch (mode) {
+        case NEW_IMAGE_MODE_EXISTING:
+            ret = 0;
+            break;
+        case NEW_IMAGE_MODE_ABSOLUTE_PATHS:
+            /* create new image with backing file */
+            ret = bdrv_img_create(target, format,
+                                  source->filename,
+                                  source->drv->format_name,
+                                  NULL, -1, flags);
+            break;
+        default:
+            abort();
+        }
+    }
+
+    if (ret) {
+        error_set(errp, QERR_OPEN_FILE_FAILED, target);
+        return;
+    }
+
+    /* ### TODO check for cluster size vs. dirty bitmap granularity */
+
+    target_bs = bdrv_new("");
+    ret = bdrv_open(target_bs, target, flags | BDRV_O_NO_BACKING, drv);
+
+    if (ret < 0) {
+        bdrv_delete(target_bs);
+        error_set(errp, QERR_OPEN_FILE_FAILED, target);
+        return;
+    }
+
+    mirror_start(bs, target_bs, speed, sync, block_job_cb, bs, &local_err);
+    if (local_err != NULL) {
+        bdrv_delete(target_bs);
+        error_propagate(errp, local_err);
+        return;
+    }
+
+    /* Grab a reference so hotplug does not delete the BlockDriverState from
+     * underneath us.
+     */
+    drive_get_ref(drive_get_by_blockdev(bs));
+}
+
+
 
 static void eject_device(BlockDriverState *bs, int force, Error **errp)
 {
@@ -1067,12 +1181,12 @@ static QObject *qobject_from_block_job(BlockJob *job)
                               job->speed);
 }
 
-static void block_stream_cb(void *opaque, int ret)
+static void block_job_cb(void *opaque, int ret)
 {
     BlockDriverState *bs = opaque;
     QObject *obj;
 
-    trace_block_stream_cb(bs, bs->job, ret);
+    trace_block_job_cb(bs, bs->job, ret);
 
     assert(bs->job);
     obj = qobject_from_block_job(bs->job);
@@ -1119,7 +1233,7 @@ void qmp_block_stream(const char *device, bool has_base,
     }
 
     stream_start(bs, base_bs, base, has_speed ? speed : 0,
-                 on_error, block_stream_cb, bs, &local_err);
+                 on_error, block_job_cb, bs, &local_err);
     if (error_is_set(&local_err)) {
         error_propagate(errp, local_err);
         return;
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 836564c..7ee8de8 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -958,6 +958,27 @@ Snapshot device, using snapshot file as target if provided
 ETEXI
 
     {
+        .name       = "drive_mirror",
+        .args_type  = "reuse:-n,full:-f,device:B,target:s,format:s?",
+        .params     = "[-n] [-f] device target [format]",
+        .help       = "initiates live storage\n\t\t\t"
+                      "migration for a device. The device's contents are\n\t\t\t"
+                      "copied to the new image file, including data that\n\t\t\t"
+                      "is written after the command is started.\n\t\t\t"
+                      "The -n flag requests QEMU to reuse the image found\n\t\t\t"
+                      "in new-image-file, instead of recreating it from scratch.\n\t\t\t"
+                      "The -f flag requests QEMU to copy the whole disk,\n\t\t\t"
+                      "so that the result does not need a backing file.\n\t\t\t",
+        .mhandler.cmd = hmp_drive_mirror,
+    },
+STEXI
+@item drive_mirror
+@findex drive_mirror
+Start mirroring a block device's writes to a new destination,
+using the specified target.
+ETEXI
+
+    {
         .name       = "drive_add",
         .args_type  = "pci_addr:s,opts:s",
         .params     = "[[<domain>:]<bus>:]<slot>\n"
diff --git a/hmp.c b/hmp.c
index b91fd32..2dfc477 100644
--- a/hmp.c
+++ b/hmp.c
@@ -693,6 +693,34 @@ void hmp_block_resize(Monitor *mon, const QDict *qdict)
     hmp_handle_error(mon, &errp);
 }
 
+void hmp_drive_mirror(Monitor *mon, const QDict *qdict)
+{
+    const char *device = qdict_get_str(qdict, "device");
+    const char *filename = qdict_get_str(qdict, "target");
+    const char *format = qdict_get_try_str(qdict, "format");
+    int reuse = qdict_get_try_bool(qdict, "reuse", 0);
+    int full = qdict_get_try_bool(qdict, "full", 0);
+    enum NewImageMode mode;
+    Error *errp = NULL;
+
+    if (!filename) {
+        error_set(&errp, QERR_MISSING_PARAMETER, "target");
+        hmp_handle_error(mon, &errp);
+        return;
+    }
+
+    if (reuse) {
+        mode = NEW_IMAGE_MODE_EXISTING;
+    } else {
+        mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
+    }
+
+    qmp_drive_mirror(device, filename, !!format, format,
+                     full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
+                     true, mode, false, 0, &errp);
+    hmp_handle_error(mon, &errp);
+}
+
 void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict)
 {
     const char *device = qdict_get_str(qdict, "device");
diff --git a/hmp.h b/hmp.h
index 6a16eec..4e86083 100644
--- a/hmp.h
+++ b/hmp.h
@@ -48,6 +48,7 @@ void hmp_block_passwd(Monitor *mon, const QDict *qdict);
 void hmp_balloon(Monitor *mon, const QDict *qdict);
 void hmp_block_resize(Monitor *mon, const QDict *qdict);
 void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict);
+void hmp_drive_mirror(Monitor *mon, const QDict *qdict);
 void hmp_migrate_cancel(Monitor *mon, const QDict *qdict);
 void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict);
 void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict);
diff --git a/qapi-schema.json b/qapi-schema.json
index 0d54c56..7b619e9 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1359,6 +1359,39 @@
   'returns': 'str' } 
 
 ##
+# @drive-mirror
+#
+# Start mirroring a block device's writes to a new destination.
+#
+# @device:  the name of the device whose writes should be mirrored.
+#
+# @target: the target of the new image. If the file exists, or if it
+#          is a device, the existing file/device will be used as the new
+#          destination.  If it does not exist, a new file will be created.
+#
+# @format: #optional the format of the new destination, default is the
+#          format of the source
+#
+# @mode: #optional whether and how QEMU should create a new image, default is
+# 'absolute-paths'.
+#
+# @sync: what parts of the disk image should be copied to the destination
+#        (all the disk, only the sectors allocated in the topmost image, or
+#        only new I/O).
+#
+# Returns: nothing on success
+#          If @device is not a valid block device, DeviceNotFound
+#          If @target can't be opened, OpenFileFailed
+#          If @format is invalid, InvalidBlockFormat
+#
+# Since 1.1
+##
+{ 'command': 'drive-mirror',
+  'data': { 'device': 'str', 'target': 'str', '*format': 'str',
+            'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
+            '*speed': 'int' } }
+
+##
 # @migrate_cancel
 #
 # Cancel the current executing migration process.
diff --git a/qmp-commands.hx b/qmp-commands.hx
index e2d77b6..cccea2f 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -832,6 +832,47 @@ Example:
 EQMP
 
     {
+        .name       = "drive-mirror",
+        .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?",
+        .mhandler.cmd_new = qmp_marshal_input_drive_mirror,
+    },
+
+SQMP
+drive-mirror
+------------
+
+Start mirroring a block device's writes to a new destination. target
+specifies the target of the new image. If the file exists, or if it is
+a device, it will be used as the new destination for writes. If does not
+exist, a new file will be created. format specifies the format of the
+mirror image, default is to probe if mode='existing', else qcow2.
+
+Arguments:
+
+- "device": device name to operate on (json-string)
+- "target": name of new image file (json-string)
+- "format": format of new image (json-string, optional)
+- "mode": how an image file should be created into the target
+  file/device (NewImageMode, optional, default 'absolute-paths')
+- "speed": maximum speed of the streaming job, in bytes per second
+  (json-int)
+- "sync": what parts of the disk image should be copied to the destination;
+  possibilities include "full" for all the disk, "top" for only the sectors
+  allocated in the topmost image, or "none" to only replicate new I/O
+  (MirrorSyncMode).
+
+
+Example:
+
+-> { "execute": "drive-mirror", "arguments": { "device": "ide-hd0",
+                                               "target": "/some/place/my-image",
+                                               "sync": "full",
+                                               "format": "qcow2" } }
+<- { "return": {} }
+
+EQMP
+
+    {
         .name       = "balloon",
         .args_type  = "value:M",
         .mhandler.cmd_new = qmp_marshal_input_balloon,
diff --git a/trace-events b/trace-events
index 2124105..306faf6 100644
--- a/trace-events
+++ b/trace-events
@@ -83,7 +83,7 @@ mirror_one_iteration(void *s, int64_t sector_num) "s %p sector_num %"PRId64
 qmp_block_job_cancel(void *job) "job %p"
 qmp_block_job_pause(void *job) "job %p"
 qmp_block_job_resume(void *job) "job %p"
-block_stream_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
+block_job_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
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 24/36] mirror: support querying target file
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (22 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 23/36] qmp: add drive-mirror command Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 25/36] mirror: add support for on_source_error/on_target_error Paolo Bonzini
                   ` (11 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

This lets query-block-jobs retrieve information and statistics on the
mirroring target.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/mirror.c |   11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/block/mirror.c b/block/mirror.c
index 1ed19a9..4c1582d 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -201,10 +201,21 @@ static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
     ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
 }
 
+static void mirror_query(BlockJob *job, BlockJobInfo *info)
+{
+    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+
+    info->has_target = true;
+    info->target = g_new0(BlockJobTargetInfo, 1);
+    info->target->info = bdrv_query_info(s->target);
+    info->target->stats = bdrv_query_stats(s->target);
+}
+
 static BlockJobType mirror_job_type = {
     .instance_size = sizeof(MirrorBlockJob),
     .job_type      = "mirror",
     .set_speed     = mirror_set_speed,
+    .query         = mirror_query,
 };
 
 void mirror_start(BlockDriverState *bs, BlockDriverState *target,
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 25/36] mirror: add support for on_source_error/on_target_error
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (23 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 24/36] mirror: support querying target file Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 21:12   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 26/36] block: live snapshot documentation tweaks Paolo Bonzini
                   ` (10 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

(Mostly untested).

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/mirror.c   |   50 +++++++++++++++++++++++++++++++++++++++++---------
 block_int.h      |    4 ++++
 blockdev.c       |   14 ++++++++++++--
 hmp.c            |    3 ++-
 qapi-schema.json |    7 ++++++-
 qmp-commands.hx  |    8 +++++++-
 6 files changed, 72 insertions(+), 14 deletions(-)

diff --git a/block/mirror.c b/block/mirror.c
index 4c1582d..bdcbe3e 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -32,11 +32,14 @@ typedef struct MirrorBlockJob {
     RateLimit limit;
     BlockDriverState *target;
     MirrorSyncMode mode;
+    BlockdevOnError on_source_error, on_target_error;
+    bool synced;
     int64_t sector_num;
     void *buf;
 } MirrorBlockJob;
 
-static int coroutine_fn mirror_iteration(MirrorBlockJob *s)
+static int coroutine_fn mirror_iteration(MirrorBlockJob *s,
+                                         BlockErrorAction *p_action)
 {
     BlockDriverState *source = s->common.bs;
     BlockDriverState *target = s->target;
@@ -58,9 +61,23 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s)
 
     ret = bdrv_co_readv(source, s->sector_num, nb_sectors, &qiov);
     if (ret < 0) {
-        return ret;
+        *p_action = block_job_error_action(&s->common, source,
+                                           s->on_source_error, true, -ret);
+        goto fail;
     }
-    return bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov);
+    ret = bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov);
+    if (ret < 0) {
+        *p_action = block_job_error_action(&s->common, target,
+                                           s->on_target_error, false, -ret);
+        s->synced = false;
+        goto fail;
+    }
+    return 0;
+
+fail:
+    /* Try again later.  */
+    bdrv_set_dirty(source, s->sector_num, nb_sectors);
+    return ret;
 }
 
 static void coroutine_fn mirror_run(void *opaque)
@@ -70,7 +87,6 @@ static void coroutine_fn mirror_run(void *opaque)
     int64_t sector_num, end;
     int ret = 0;
     int n;
-    bool synced = false;
 
     if (block_job_is_cancelled(&s->common)) {
         goto immediate_exit;
@@ -116,8 +132,9 @@ static void coroutine_fn mirror_run(void *opaque)
         bool should_complete;
 
         if (bdrv_get_dirty_count(bs) != 0) {
-            ret = mirror_iteration(s);
-            if (ret < 0) {
+            BlockErrorAction action = BDRV_ACTION_REPORT;
+            ret = mirror_iteration(s, &action);
+            if (ret < 0 && action == BDRV_ACTION_REPORT) {
                 break;
             }
         }
@@ -128,11 +145,11 @@ static void coroutine_fn mirror_run(void *opaque)
              * I/O and report completion, so that drive-reopen can be
              * used to pivot to the mirroring target.
              */
-            synced = true;
+            s->synced = true;
             s->common.offset = end * BDRV_SECTOR_SIZE;
         }
 
-        should_complete = synced && block_job_is_cancelled(&s->common);
+        should_complete = s->synced && block_job_is_cancelled(&s->common);
         if (should_complete) {
             /* The dirty bitmap is not updated while operations are pending.
              * If we're about to exit, wait for pending operations before
@@ -147,7 +164,7 @@ static void coroutine_fn mirror_run(void *opaque)
 
         ret = 0;
         cnt = bdrv_get_dirty_count(bs);
-        if (synced) {
+        if (s->synced) {
             if (!should_complete) {
                 delay_ns = (cnt == 0 ? SLICE_TIME : 0);
                 block_job_sleep_ns(&s->common, rt_clock, delay_ns);
@@ -185,6 +202,7 @@ static void coroutine_fn mirror_run(void *opaque)
 immediate_exit:
     g_free(s->buf);
     bdrv_set_dirty_tracking(bs, false);
+    bdrv_iostatus_disable(s->target);
     bdrv_close(s->target);
     bdrv_delete(s->target);
     block_job_completed(&s->common, ret);
@@ -201,6 +219,13 @@ static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
     ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
 }
 
+static void mirror_iostatus_reset(BlockJob *job)
+{
+    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+
+    bdrv_iostatus_reset(s->target);
+}
+
 static void mirror_query(BlockJob *job, BlockJobInfo *info)
 {
     MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
@@ -215,11 +240,14 @@ static BlockJobType mirror_job_type = {
     .instance_size = sizeof(MirrorBlockJob),
     .job_type      = "mirror",
     .set_speed     = mirror_set_speed,
+    .iostatus_reset= mirror_iostatus_reset,
     .query         = mirror_query,
 };
 
 void mirror_start(BlockDriverState *bs, BlockDriverState *target,
                   int64_t speed, MirrorSyncMode mode,
+                  BlockdevOnError on_source_error,
+                  BlockdevOnError on_target_error,
                   BlockDriverCompletionFunc *cb,
                   void *opaque, Error **errp)
 {
@@ -230,9 +258,13 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
         return;
     }
 
+    s->on_source_error = on_source_error;
+    s->on_target_error = on_target_error;
     s->target = target;
     s->mode = mode;
     bdrv_set_dirty_tracking(bs, true);
+    bdrv_set_on_error(s->target, on_target_error, on_target_error);
+    bdrv_iostatus_enable(s->target);
     s->common.co = qemu_coroutine_create(mirror_run);
     trace_mirror_start(bs, s, s->common.co, opaque);
     qemu_coroutine_enter(s->common.co, s);
diff --git a/block_int.h b/block_int.h
index d7e1928..599eb37 100644
--- a/block_int.h
+++ b/block_int.h
@@ -312,6 +312,8 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
  * @target: Block device to write to.
  * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
  * @mode: Whether to collapse all images in the chain to the target.
+ * @on_source_error: The action to take upon error reading from the source.
+ * @on_target_error: The action to take upon error writing to the target.
  * @cb: Completion function for the job.
  * @opaque: Opaque pointer value passed to @cb.
  * @errp: Error object.
@@ -323,6 +325,8 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
  */
 void mirror_start(BlockDriverState *bs, BlockDriverState *target,
                   int64_t speed, MirrorSyncMode mode,
+                  BlockdevOnError on_source_error,
+                  BlockdevOnError on_target_error,
                   BlockDriverCompletionFunc *cb,
                   void *opaque, Error **errp);
 
diff --git a/blockdev.c b/blockdev.c
index 29ecadc..4000d16 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -835,7 +835,10 @@ void qmp_drive_mirror(const char *device, const char *target,
                       bool has_format, const char *format,
                       enum MirrorSyncMode sync,
                       bool has_mode, enum NewImageMode mode,
-                      bool has_speed, int64_t speed, Error **errp)
+                      bool has_speed, int64_t speed,
+                      bool has_on_source_error, BlockdevOnError on_source_error,
+                      bool has_on_target_error, BlockdevOnError on_target_error,
+                      Error **errp)
 {
     BlockDriverState *bs;
     BlockDriverState *source, *target_bs;
@@ -846,6 +849,12 @@ void qmp_drive_mirror(const char *device, const char *target,
     uint64_t size;
     int ret;
 
+    if (!has_on_source_error) {
+        on_source_error = BLOCKDEV_ON_ERROR_REPORT;
+    }
+    if (!has_on_target_error) {
+        on_target_error = BLOCKDEV_ON_ERROR_REPORT;
+    }
     if (!has_mode) {
         mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
     }
@@ -929,7 +938,8 @@ void qmp_drive_mirror(const char *device, const char *target,
         return;
     }
 
-    mirror_start(bs, target_bs, speed, sync, block_job_cb, bs, &local_err);
+    mirror_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
+                 block_job_cb, bs, &local_err);
     if (local_err != NULL) {
         bdrv_delete(target_bs);
         error_propagate(errp, local_err);
diff --git a/hmp.c b/hmp.c
index 2dfc477..ce0d040 100644
--- a/hmp.c
+++ b/hmp.c
@@ -717,7 +717,8 @@ void hmp_drive_mirror(Monitor *mon, const QDict *qdict)
 
     qmp_drive_mirror(device, filename, !!format, format,
                      full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
-                     true, mode, false, 0, &errp);
+                     true, mode, false, 0,
+                     false, 0, false, 0, &errp);
     hmp_handle_error(mon, &errp);
 }
 
diff --git a/qapi-schema.json b/qapi-schema.json
index 7b619e9..2ee988b 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1379,6 +1379,10 @@
 #        (all the disk, only the sectors allocated in the topmost image, or
 #        only new I/O).
 #
+# @on_source_error: #optional the action to take on an error on the source
+#
+# @on_target_error: #optional the action to take on an error on the target
+#
 # Returns: nothing on success
 #          If @device is not a valid block device, DeviceNotFound
 #          If @target can't be opened, OpenFileFailed
@@ -1389,7 +1393,8 @@
 { 'command': 'drive-mirror',
   'data': { 'device': 'str', 'target': 'str', '*format': 'str',
             'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
-            '*speed': 'int' } }
+            '*speed': 'int', '*on_source_error': 'BlockdevOnError',
+            '*on_target_error': 'BlockdevOnError' } }
 
 ##
 # @migrate_cancel
diff --git a/qmp-commands.hx b/qmp-commands.hx
index cccea2f..aff1660 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -833,7 +833,8 @@ EQMP
 
     {
         .name       = "drive-mirror",
-        .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?",
+        .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?,"
+                      "on_source_error:s?,on_target_error:s?",
         .mhandler.cmd_new = qmp_marshal_input_drive_mirror,
     },
 
@@ -860,6 +861,11 @@ Arguments:
   possibilities include "full" for all the disk, "top" for only the sectors
   allocated in the topmost image, or "none" to only replicate new I/O
   (MirrorSyncMode).
+- "on_source_error": the action to take on an error on the source
+  (BlockdevOnError)
+- "on_target_error": the action to take on an error on the target
+  (BlockdevOnError)
+
 
 
 Example:
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 26/36] block: live snapshot documentation tweaks
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (24 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 25/36] mirror: add support for on_source_error/on_target_error Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 21:14   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 27/36] block: add bdrv_ensure_backing_file Paolo Bonzini
                   ` (9 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 qapi-schema.json |    4 ++++
 1 file changed, 4 insertions(+)

diff --git a/qapi-schema.json b/qapi-schema.json
index 2ee988b..250edf2 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1289,6 +1289,7 @@
 #
 # Returns: nothing on success
 #          If @device is not a valid block device, DeviceNotFound
+#          If the block device has no medium inserted, DeviceHasNoMedium
 #          If @device is busy, DeviceInUse will be returned
 #          If @snapshot-file can't be created, OpenFileFailed
 #          If @snapshot-file can't be opened, OpenFileFailed
@@ -1321,6 +1322,9 @@
 #
 # Returns: nothing on success
 #          If @device is not a valid block device, DeviceNotFound
+#          If the block device has no medium inserted, DeviceHasNoMedium
+#          If @device is busy, DeviceInUse will be returned
+#          If @snapshot-file can't be created, OpenFileFailed
 #          If @snapshot-file can't be opened, OpenFileFailed
 #          If @format is invalid, InvalidBlockFormat
 #
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 27/36] block: add bdrv_ensure_backing_file
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (25 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 26/36] block: live snapshot documentation tweaks Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 28/36] block: add block-job-complete Paolo Bonzini
                   ` (8 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Mirroring runs without the backing file so that it can be copied outside
QEMU.  However, we need to add it at the time the job is completed and
QEMU switches to the target.  Factor out the common bits of opening an
image and completing a mirroring operation.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c |   69 ++++++++++++++++++++++++++++++++++++++++-----------------------
 block.h |    1 +
 2 files changed, 45 insertions(+), 25 deletions(-)

diff --git a/block.c b/block.c
index dbb9041..5b08c5b 100644
--- a/block.c
+++ b/block.c
@@ -730,6 +730,48 @@ int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags)
     return 0;
 }
 
+int bdrv_ensure_backing_file(BlockDriverState *bs)
+{
+    char backing_filename[PATH_MAX];
+    int back_flags, ret;
+    BlockDriver *back_drv = NULL;
+
+    if (bs->backing_hd != NULL) {
+        return 0;
+    }
+
+    bs->open_flags &= ~BDRV_O_NO_BACKING;
+    if (bs->backing_file[0] == '\0') {
+        return 0;
+    }
+
+    bs->backing_hd = bdrv_new("");
+    bdrv_get_full_backing_filename(bs, backing_filename,
+                                   sizeof(backing_filename));
+
+    if (bs->backing_format[0] != '\0') {
+        back_drv = bdrv_find_format(bs->backing_format);
+    }
+
+    /* backing files always opened read-only */
+    back_flags = bs->open_flags & ~(BDRV_O_RDWR | BDRV_O_SNAPSHOT);
+
+    ret = bdrv_open(bs->backing_hd, backing_filename, back_flags, back_drv);
+    if (ret < 0) {
+        bdrv_close(bs);
+        bdrv_delete(bs->backing_hd);
+        bs->backing_hd = NULL;
+        return ret;
+    }
+    if (bs->is_temporary) {
+        bs->backing_hd->keep_read_only = !(bs->open_flags & BDRV_O_RDWR);
+    } else {
+        /* base images use the same setting as leaf */
+        bs->backing_hd->keep_read_only = bs->keep_read_only;
+    }
+    return 0;
+}
+
 /*
  * Opens a disk image (raw, qcow2, vmdk, ...)
  */
@@ -813,34 +855,11 @@ int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
     }
 
     /* If there is a backing file, use it */
-    if ((flags & BDRV_O_NO_BACKING) == 0 && bs->backing_file[0] != '\0') {
-        char backing_filename[PATH_MAX];
-        int back_flags;
-        BlockDriver *back_drv = NULL;
-
-        bs->backing_hd = bdrv_new("");
-        bdrv_get_full_backing_filename(bs, backing_filename,
-                                       sizeof(backing_filename));
-
-        if (bs->backing_format[0] != '\0') {
-            back_drv = bdrv_find_format(bs->backing_format);
-        }
-
-        /* backing files always opened read-only */
-        back_flags =
-            flags & ~(BDRV_O_RDWR | BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING);
-
-        ret = bdrv_open(bs->backing_hd, backing_filename, back_flags, back_drv);
+    if ((flags & BDRV_O_NO_BACKING) == 0) {
+        ret = bdrv_ensure_backing_file(bs);
         if (ret < 0) {
-            bdrv_close(bs);
             return ret;
         }
-        if (bs->is_temporary) {
-            bs->backing_hd->keep_read_only = !(flags & BDRV_O_RDWR);
-        } else {
-            /* base image inherits from "parent" */
-            bs->backing_hd->keep_read_only = bs->keep_read_only;
-        }
     }
 
     if (!bdrv_key_required(bs)) {
diff --git a/block.h b/block.h
index c13d779..a4aea47 100644
--- a/block.h
+++ b/block.h
@@ -122,6 +122,7 @@ void bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top);
 void bdrv_delete(BlockDriverState *bs);
 int bdrv_parse_cache_flags(const char *mode, int *flags);
 int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags);
+int bdrv_ensure_backing_file(BlockDriverState *bs);
 int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
               BlockDriver *drv);
 void bdrv_close(BlockDriverState *bs);
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 28/36] block: add block-job-complete
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (26 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 27/36] block: add bdrv_ensure_backing_file Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 21:42   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 29/36] mirror: implement completion Paolo Bonzini
                   ` (7 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

While streaming can be dropped as soon as it progressed through the whole
image, mirroring needs to be completed manually for two reasons: 1) so that
management knows exactly when the VM switches to the target; 2) because
for other use cases such as replication, we may leave the operation running
for the whole life of the virtual machine.

Add a new block job command that manually completes background operations.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockdev.c       |   13 +++++++++++++
 blockjob.c       |   10 ++++++++++
 blockjob.h       |   15 +++++++++++++++
 hmp-commands.hx  |   17 ++++++++++++++++-
 hmp.c            |   10 ++++++++++
 hmp.h            |    1 +
 qapi-schema.json |   27 +++++++++++++++++++++++++++
 qerror.c         |    4 ++++
 qerror.h         |    3 +++
 qmp-commands.hx  |    5 +++++
 trace-events     |    1 +
 11 files changed, 105 insertions(+), 1 deletion(-)

diff --git a/blockdev.c b/blockdev.c
index 4000d16..b46a86c 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1328,6 +1328,19 @@ void qmp_block_job_resume(const char *device, Error **errp)
     block_job_resume(job);
 }
 
+void qmp_block_job_complete(const char *device, Error **errp)
+{
+    BlockJob *job = find_block_job(device);
+
+    if (!job) {
+        error_set(errp, QERR_BLOCK_JOB_NOT_ACTIVE, device);
+        return;
+    }
+
+    trace_qmp_block_job_complete(job);
+    block_job_complete(job, errp);
+}
+
 static void do_qmp_query_block_jobs_one(void *opaque, BlockDriverState *bs)
 {
     BlockJobInfoList **prev = opaque;
diff --git a/blockjob.c b/blockjob.c
index d3819df..602e669 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -99,6 +99,16 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
     job->speed = speed;
 }
 
+void block_job_complete(BlockJob *job, Error **errp)
+{
+    if (job->paused || job->cancelled || !job->job_type->complete) {
+       error_set(errp, QERR_BLOCK_JOB_NOT_READY, job->bs->device_name);
+       return;
+    }
+
+    job->job_type->complete(job, errp);
+}
+
 void block_job_pause(BlockJob *job)
 {
     job->paused = true;
diff --git a/blockjob.h b/blockjob.h
index 02e0363..563b0a2 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -52,6 +52,12 @@ typedef struct BlockJobType {
      * of BlockJobInfo.
      */
     void (*query)(BlockJob *job, BlockJobInfo *info);
+
+    /**
+     * Optional callback for job types whose completion must be triggered
+     * manually.
+     */
+    void (*complete)(BlockJob *job, Error **errp);
 } BlockJobType;
 
 /**
@@ -175,6 +181,15 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
 void block_job_cancel(BlockJob *job);
 
 /**
+ * block_job_complete:
+ * @job: The job to be completed.
+ * @errp: Error object.
+ *
+ * Synchronously complete the specified job.
+ */
+void block_job_complete(BlockJob *job, Error **errp);
+
+/**
  * block_job_is_cancelled:
  * @job: The job being queried.
  *
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 7ee8de8..47827bf 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -109,7 +109,22 @@ ETEXI
 STEXI
 @item block_job_cancel
 @findex block_job_cancel
-Stop an active block streaming operation.
+Stop an active background block operation (streaming, mirroring).
+ETEXI
+
+    {
+        .name       = "block_job_complete",
+        .args_type  = "device:B",
+        .params     = "device",
+        .help       = "stop an active background block operation",
+        .mhandler.cmd = hmp_block_job_complete,
+    },
+
+STEXI
+@item block_job_complete
+@findex block_job_complete
+Manually trigger completion of an active background block operation.
+For mirroring, this will switch the device to the destination path.
 ETEXI
 
     {
diff --git a/hmp.c b/hmp.c
index ce0d040..ef0b87f 100644
--- a/hmp.c
+++ b/hmp.c
@@ -920,6 +920,16 @@ void hmp_block_job_resume(Monitor *mon, const QDict *qdict)
     hmp_handle_error(mon, &error);
 }
 
+void hmp_block_job_complete(Monitor *mon, const QDict *qdict)
+{
+    Error *error = NULL;
+    const char *device = qdict_get_str(qdict, "device");
+
+    qmp_block_job_complete(device, &error);
+
+    hmp_handle_error(mon, &error);
+}
+
 typedef struct MigrationStatus
 {
     QEMUTimer *timer;
diff --git a/hmp.h b/hmp.h
index 4e86083..61fadc1 100644
--- a/hmp.h
+++ b/hmp.h
@@ -62,6 +62,7 @@ void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
 void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
 void hmp_block_job_resume(Monitor *mon, const QDict *qdict);
+void hmp_block_job_complete(Monitor *mon, const QDict *qdict);
 void hmp_migrate(Monitor *mon, const QDict *qdict);
 void hmp_device_del(Monitor *mon, const QDict *qdict);
 void hmp_dump_guest_memory(Monitor *mon, const QDict *qdict);
diff --git a/qapi-schema.json b/qapi-schema.json
index 250edf2..bbcfa0e 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1840,6 +1840,33 @@
 { 'command': 'block-job-resume', 'data': { 'device': 'str' } }
 
 ##
+# @block-job-complete:
+#
+# Manually trigger completion of an active background block operation.  This
+# is supported for drive mirroring, where it also switches the device to
+# write to the target path only.
+#
+# This command completes an active background block operation synchronously.
+# The ordering of this command's return with the BLOCK_JOB_COMPLETED event
+# is not defined.  Note that if an I/O error occurs during the processing of
+# this command: 1) the command itself will fail; 2) the error will be processed
+# according to the rerror/werror arguments that were specified when starting
+# the operation.
+#
+# A cancelled or paused job cannot be completed.
+#
+# @device: the device name
+#
+# Returns: Nothing on success
+#          If no background operation is active on this device, BlockJobNotActive
+#          If the operation cannot be completed manually (either in general, or
+#            not at the time the command is invoked), BlockJobNotReady
+#
+# Since: 1.1
+##
+{ 'command': 'block-job-complete', 'data': { 'device': 'str' } }
+
+##
 # @ObjectTypeInfo:
 #
 # This structure describes a search result from @qom-list-types
diff --git a/qerror.c b/qerror.c
index 72183ec..60303a4 100644
--- a/qerror.c
+++ b/qerror.c
@@ -68,6 +68,10 @@ static const QErrorStringTable qerror_table[] = {
         .desc      = "The block job for device '%(name)' is currently paused",
     },
     {
+        .error_fmt = QERR_BLOCK_JOB_NOT_READY,
+        .desc      = "The active block job for device '%(name)' cannot be completed",
+    },
+    {
         .error_fmt = QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
         .desc      = "Block format '%(format)' used by device '%(name)' does not support feature '%(feature)'",
     },
diff --git a/qerror.h b/qerror.h
index d1baea0..c15e933 100644
--- a/qerror.h
+++ b/qerror.h
@@ -70,6 +70,9 @@ QError *qobject_to_qerror(const QObject *obj);
 #define QERR_BLOCK_JOB_PAUSED \
     "{ 'class': 'BlockJobPaused', 'data': { 'name': %s } }"
 
+#define QERR_BLOCK_JOB_NOT_READY \
+    "{ 'class': 'BlockJobNotReady', 'data': { 'name': %s } }"
+
 #define QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED \
     "{ 'class': 'BlockFormatFeatureNotSupported', 'data': { 'format': %s, 'name': %s, 'feature': %s } }"
 
diff --git a/qmp-commands.hx b/qmp-commands.hx
index aff1660..f1b0f90 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -744,6 +744,11 @@ EQMP
         .mhandler.cmd_new = qmp_marshal_input_block_job_resume,
     },
     {
+        .name       = "block-job-complete",
+        .args_type  = "device:B",
+        .mhandler.cmd_new = qmp_marshal_input_block_job_complete,
+    },
+    {
         .name       = "transaction",
         .args_type  = "actions:q",
         .mhandler.cmd_new = qmp_marshal_input_transaction,
diff --git a/trace-events b/trace-events
index 306faf6..65db490 100644
--- a/trace-events
+++ b/trace-events
@@ -83,6 +83,7 @@ mirror_one_iteration(void *s, int64_t sector_num) "s %p sector_num %"PRId64
 qmp_block_job_cancel(void *job) "job %p"
 qmp_block_job_pause(void *job) "job %p"
 qmp_block_job_resume(void *job) "job %p"
+qmp_block_job_complete(void *job) "job %p"
 block_job_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
 qmp_block_stream(void *bs, void *job) "bs %p job %p"
 
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 29/36] mirror: implement completion
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (27 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 28/36] block: add block-job-complete Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 30/36] add hierarchical bitmap data type and test cases Paolo Bonzini
                   ` (6 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Switching to the target of the migration is done mostly asynchronously,
and reported to management via the BLOCK_JOB_COMPLETED event; the only
synchronous phase is opening the backing files.  Note that this can be
done always, even for migration of the full image, because the backing
file structure of the source and target are not in any relationship.
For full migration (aka sync: 'full') qmp_drive_mirror will create the
target disk with no backing file at all, and bdrv_ensure_backing_file
will be a no-op.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/mirror.c |   37 +++++++++++++++++++++++++++++++++++--
 1 file changed, 35 insertions(+), 2 deletions(-)

diff --git a/block/mirror.c b/block/mirror.c
index bdcbe3e..346a0eb 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -34,6 +34,7 @@ typedef struct MirrorBlockJob {
     MirrorSyncMode mode;
     BlockdevOnError on_source_error, on_target_error;
     bool synced;
+    bool complete;
     int64_t sector_num;
     void *buf;
 } MirrorBlockJob;
@@ -149,7 +150,9 @@ static void coroutine_fn mirror_run(void *opaque)
             s->common.offset = end * BDRV_SECTOR_SIZE;
         }
 
-        should_complete = s->synced && block_job_is_cancelled(&s->common);
+        should_complete =
+            s->synced && (block_job_is_cancelled(&s->common) || s->complete);
+
         if (should_complete) {
             /* The dirty bitmap is not updated while operations are pending.
              * If we're about to exit, wait for pending operations before
@@ -203,7 +206,14 @@ immediate_exit:
     g_free(s->buf);
     bdrv_set_dirty_tracking(bs, false);
     bdrv_iostatus_disable(s->target);
-    bdrv_close(s->target);
+    if (s->synced && ret == 0) {
+        ret = bdrv_flush(s->target);
+    }
+    if (s->synced && ret == 0) {
+        bdrv_swap(s->target, s->common.bs);
+    } else {
+        bdrv_close(s->target);
+    }
     bdrv_delete(s->target);
     block_job_completed(&s->common, ret);
 }
@@ -236,12 +246,35 @@ static void mirror_query(BlockJob *job, BlockJobInfo *info)
     info->target->stats = bdrv_query_stats(s->target);
 }
 
+static void mirror_complete(BlockJob *job, Error **errp)
+{
+    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+    int ret;
+
+    ret = bdrv_ensure_backing_file(s->target);
+    if (ret < 0) {
+        char backing_filename[PATH_MAX];
+        bdrv_get_full_backing_filename(s->target, backing_filename,
+                                       sizeof(backing_filename));
+        error_set(errp, QERR_OPEN_FILE_FAILED, backing_filename);
+        return;
+    }
+    if (!s->synced) {
+        error_set(errp, QERR_BLOCK_JOB_NOT_READY, job->bs->device_name);
+        return;
+    }
+
+    s->complete = true;
+    block_job_resume(job);
+}
+
 static BlockJobType mirror_job_type = {
     .instance_size = sizeof(MirrorBlockJob),
     .job_type      = "mirror",
     .set_speed     = mirror_set_speed,
     .iostatus_reset= mirror_iostatus_reset,
     .query         = mirror_query,
+    .complete      = mirror_complete,
 };
 
 void mirror_start(BlockDriverState *bs, BlockDriverState *target,
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 30/36] add hierarchical bitmap data type and test cases
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (28 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 29/36] mirror: implement completion Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 23:02   ` Eric Blake
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 31/36] block: implement dirty bitmap using HBitmap Paolo Bonzini
                   ` (5 subsequent siblings)
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

HBitmaps provides an array of bits.  The bits are stored as usual in an
array of unsigned longs, but HBitmap is also optimized to provide fast
iteration over set bits; going from one bit to the next is O(log log n)
worst case, which is low enough that the number of levels is in fact fixed.

In order to do this, it stacks multiple bitmaps with progressively coarser
granularity; in all levels except the last, bit N is set iff the N-th
unsigned long is nonzero in the immediately next level.  When iteration
completes on the last level it can examine the 2nd-last level to quickly
skip entire words, and even do so recursively to skip blocks of 64 words or
powers thereof (32 on 32-bit machines).

Given an index in the bitmap, it can be split in group of bits like
this (for the 64-bit case):

     bits 0-57 => word in the last bitmap     | bits 58-63 => bit in the word
     bits 0-51 => word in the 2nd-last bitmap | bits 52-57 => bit in the word
     bits 0-45 => word in the 3rd-last bitmap | bits 46-51 => bit in the word

So it is easy to move up simply by shifting the index right by
log2(BITS_PER_LONG) bits.  To move down, you shift the index left
similarly, and add the word index within the group.  Iteration uses
ffs (find first set bit) to find the next word to examine; this
operation can be done in constant time in most current architectures.

Setting or clearing a range of m bits on all levels, the work to perform
is O(m + m/W + m/W^2 + ...), which in O(m) like on a regular bitmap.

When iterating on a bitmap, each bit (on any level) is only visited
once.  Hence, The total cost of visiting a bitmap with m bits in it is
the number of bits that are set in all bitmaps.  Unless the bitmap is
extremely sparse, this is also O(m + m/W + m/W^2 + ...), so the amortized
cost of advancing from one bit to the next is usually constant.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 hbitmap.c            |  371 ++++++++++++++++++++++++++++++++++++++++++++++++++
 hbitmap.h            |   51 +++++++
 tests/Makefile       |    2 +
 tests/test-hbitmap.c |  369 +++++++++++++++++++++++++++++++++++++++++++++++++
 trace-events         |    5 +
 5 files changed, 798 insertions(+)
 create mode 100644 hbitmap.c
 create mode 100644 hbitmap.h
 create mode 100644 tests/test-hbitmap.c

diff --git a/hbitmap.c b/hbitmap.c
new file mode 100644
index 0000000..be1a852
--- /dev/null
+++ b/hbitmap.c
@@ -0,0 +1,371 @@
+/*
+ * Hierarchical Bitmap Data Type
+ *
+ * Copyright Red Hat, Inc., 2012
+ *
+ * Author: Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later.  See the COPYING file in the top-level directory.
+ */
+
+#include "osdep.h"
+#include "hbitmap.h"
+#include "host-utils.h"
+#include "trace.h"
+#include <string.h>
+#include <glib.h>
+#include <assert.h>
+
+/* HBitmaps provides an array of bits.  The bits are stored as usual in an
+ * array of unsigned longs, but HBitmap is also optimized to provide fast
+ * iteration over set bits; going from one bit to the next is O(log log n)
+ * worst case, which is low enough that the number of levels is in fact fixed.
+ *
+ * In order to do this, it stacks multiple bitmaps with progressively coarser
+ * granularity; in all levels except the last, bit N is set iff the N-th
+ * unsigned long is nonzero in the immediately next level.  When iteration
+ * completes on the last level it can examine the 2nd-last level to quickly
+ * skip entire words, and even do so recursively to skip blocks of 64 words or
+ * powers thereof (32 on 32-bit machines).
+ *
+ * Given an index in the bitmap, it can be split in group of bits like
+ * this (for the 64-bit case):
+ *
+ *   bits 0-57 => word in the last bitmap     | bits 58-63 => bit in the word
+ *   bits 0-51 => word in the 2nd-last bitmap | bits 52-57 => bit in the word
+ *   bits 0-45 => word in the 3rd-last bitmap | bits 46-51 => bit in the word
+ *
+ * So it is easy to move up simply by shifting the index right by
+ * log2(BITS_PER_LONG) bits.  To move down, you shift the index left
+ * similarly, and add the word index within the group.  Iteration uses
+ * ffs (find first set bit) to find the next word to examine; this
+ * operation can be done in constant time in most current architectures.
+ *
+ * Setting or clearing a range of m bits on all levels, the work to perform
+ * is O(m + m/W + m/W^2 + ...), which in O(m) like on a regular bitmap.
+ *
+ * When iterating on a bitmap, each bit (on any level) is only visited
+ * once.  Hence, The total cost of visiting a bitmap with m bits in it is
+ * the number of bits that are set in all bitmaps.  Unless the bitmap is
+ * extremely sparse, this is also O(m + m/W + m/W^2 + ...), so the amortized
+ * cost of advancing from one bit to the next is usually constant (worst case
+ * O(log log n) as in the non-amortized complexity).
+ */
+
+struct HBitmap {
+    uint64_t size;
+    uint64_t count;
+    int granularity;
+    unsigned long *levels[HBITMAP_LEVELS];
+};
+
+static int64_t hbi_next_internal(HBitmapIter *hbi)
+{
+    unsigned long cur = hbi->cur[HBITMAP_LEVELS - 1];
+    size_t pos = hbi->pos;
+
+    if (cur == 0) {
+        HBitmap *hb = hbi->hb;
+        int i = HBITMAP_LEVELS - 1;
+
+        do {
+            cur = hbi->cur[--i];
+            pos >>= BITS_PER_LEVEL;
+        } while (cur == 0);
+
+        /* Check for end of iteration.  We only use up to
+         * BITS_PER_LEVEL bits (actually less) in the level 0 bitmap,
+         * and a sentinel is placed in hbitmap_alloc that ends the
+         * above loop.
+         */
+
+        if (i == 0 && (cur & (BITS_PER_LONG - 1)) == 0) {
+            return -1;
+        }
+        for (; i < HBITMAP_LEVELS - 1; i++) {
+            /* Find least significant set bit in the word, use them
+             * to add back shifted out bits to pos.
+             */
+            pos = (pos << BITS_PER_LEVEL) + ffsl(cur) - 1;
+            hbi->cur[i] = cur & (cur - 1);
+
+            /* Set up next level for iteration.  */
+            cur = hb->levels[i + 1][pos];
+        }
+
+        hbi->pos = pos;
+        hbi->cur[HBITMAP_LEVELS - 1] = cur & (cur - 1);
+    } else {
+        hbi->cur[HBITMAP_LEVELS - 1] &= cur - 1;
+    }
+    return ((uint64_t)pos << BITS_PER_LEVEL) + ffsl(cur) - 1;
+}
+
+static inline int popcountl(unsigned long l)
+{
+    return BITS_PER_LONG == 32 ? ctpop32(l) : ctpop64(l);
+}
+
+static int hbi_count_towards(HBitmapIter *hbi, uint64_t last)
+{
+    uint64_t next = hbi_next_internal(hbi);
+    int n;
+
+    /* Take it easy with the last few bits.  */
+    if (next >= (last & -BITS_PER_LONG)) {
+        return (next > last ? 0 : 1);
+    }
+
+    /* Process one word at a time, hbi_next_internal takes
+     * care of skipping large all-zero blocks.  Sum one to
+     * account for the value that was returned by next.
+     */
+    n = popcountl(hbi->cur[HBITMAP_LEVELS - 1]) + 1;
+    hbi->cur[HBITMAP_LEVELS - 1] = 0;
+    return n;
+}
+
+int64_t hbitmap_iter_next(HBitmapIter *hbi)
+{
+    int64_t next = hbi_next_internal(hbi);
+    trace_hbitmap_iter_next(hbi->hb, hbi, next << hbi->granularity, next);
+
+    return next << hbi->granularity;
+}
+
+void hbitmap_iter_init(HBitmapIter *hbi, HBitmap *hb, uint64_t first)
+{
+    int i, bit;
+    size_t pos;
+
+    hbi->hb = hb;
+    pos = first;
+    for (i = HBITMAP_LEVELS; --i >= 0; ) {
+        bit = pos & (BITS_PER_LONG - 1);
+        pos >>= BITS_PER_LEVEL;
+
+        /* Drop bits representing items before first.  */
+        hbi->cur[i] = hb->levels[i][pos] & ~((1UL << bit) - 1);
+
+        /* We have already added level i+1, so the lowest set bit has
+         * been processed.  Clear it.
+         */
+        if (i != HBITMAP_LEVELS - 1) {
+            hbi->cur[i] &= ~(1UL << bit);
+        }
+    }
+
+    hbi->pos = first >> BITS_PER_LEVEL;
+    hbi->granularity = hb->granularity;
+}
+
+bool hbitmap_empty(HBitmap *hb)
+{
+    return hb->count == 0;
+}
+
+uint64_t hbitmap_count(HBitmap *hb)
+{
+    return hb->count << hb->granularity;
+}
+
+/* Count the number of set bits between start and end, not accounting for
+ * the granularity.
+ */
+static int hb_count_between(HBitmap *hb, uint64_t start, uint64_t end)
+{
+    HBitmapIter hbi;
+    uint64_t count = 0, more;
+
+    hbitmap_iter_init(&hbi, hb, start);
+    do {
+        more = hbi_count_towards(&hbi, end);
+        count += more;
+    } while (more > 0);
+    return count;
+}
+
+/* Setting starts at the last layer and propagates up if an element
+ * changes from zero to non-zero.
+ */
+static inline bool hb_set_elem(unsigned long *elem, uint64_t start, uint64_t end)
+{
+    unsigned long mask;
+    bool changed;
+
+    assert((end & -BITS_PER_LONG) == (start & -BITS_PER_LONG));
+
+    mask = 2UL << (end & (BITS_PER_LONG - 1));
+    mask -= 1UL << (start & (BITS_PER_LONG - 1));
+    changed = (*elem == 0);
+    *elem |= mask;
+    return changed;
+}
+
+/* The recursive workhorse... */
+static void hb_set_between(HBitmap *hb, int level, uint64_t start, uint64_t end)
+{
+    size_t pos = start >> BITS_PER_LEVEL;
+    size_t endpos = end >> BITS_PER_LEVEL;
+    bool changed = false;
+    size_t i;
+
+    i = pos;
+    if (i < endpos) {
+        uint64_t next = (start | (BITS_PER_LONG - 1)) + 1;
+        changed |= hb_set_elem(&hb->levels[level][i], start, next - 1);
+        for (;;) {
+            start = next;
+            next += BITS_PER_LONG;
+            if (++i == endpos) {
+                break;
+            }
+            changed |= (hb->levels[level][i] == 0);
+            hb->levels[level][i] = ~0UL;
+        }
+    }
+    changed |= hb_set_elem(&hb->levels[level][i], start, end);
+
+    /* If there was any change in this layer, we may have to update
+     * the one above.
+     */
+    if (level > 0 && changed) {
+        return hb_set_between(hb, level - 1, pos, endpos);
+    }
+}
+
+void hbitmap_set(HBitmap *hb, uint64_t start, uint64_t count)
+{
+    /* Compute range in the last layer.  */
+    uint64_t last = start + count - 1;
+
+    trace_hbitmap_set(hb, start, count,
+                      start >> hb->granularity, last >> hb->granularity);
+
+    start >>= hb->granularity;
+    last >>= hb->granularity;
+    count = last - start + 1;
+
+    hb->count += count - hb_count_between(hb, start, last);
+    hb_set_between(hb, HBITMAP_LEVELS - 1, start, last);
+}
+
+/* Resetting works the other way round: propagate up if the new
+ * value is zero.
+ */
+static inline bool hb_reset_elem(unsigned long *elem, uint64_t start, uint64_t end)
+{
+    unsigned long mask;
+    bool blanked;
+
+    assert((end & -BITS_PER_LONG) == (start & -BITS_PER_LONG));
+
+    mask = 2UL << (end & (BITS_PER_LONG - 1));
+    mask -= 1UL << (start & (BITS_PER_LONG - 1));
+    blanked = *elem != 0 && ((*elem & ~mask) == 0);
+    *elem &= ~mask;
+    return blanked;
+}
+
+/* The recursive workhorse... */
+static void hb_reset_between(HBitmap *hb, int level, uint64_t start, uint64_t end)
+{
+    size_t pos = start >> BITS_PER_LEVEL;
+    size_t endpos = end >> BITS_PER_LEVEL;
+    bool changed = false;
+    size_t i;
+
+    i = pos;
+    if (i < endpos) {
+        uint64_t next = (start | (BITS_PER_LONG - 1)) + 1;
+
+	/* Here we need a more complex test than when setting bits.  Even if
+	 * something was changed, we must not blank bits in the upper level
+	 * unless the lower-level word became entirely zero.  So, remove pos
+	 * from the upper-level range if bits remain set.
+         */
+        if (hb_reset_elem(&hb->levels[level][i], start, next - 1)) {
+            changed = true;
+        } else {
+            pos++;
+        }
+
+        for (;;) {
+            start = next;
+            next += BITS_PER_LONG;
+            if (++i == endpos) {
+                break;
+            }
+            changed |= (hb->levels[level][i] != 0);
+            hb->levels[level][i] = 0UL;
+        }
+    }
+
+    /* Same as above, this time for endpos.  */
+    if (hb_reset_elem(&hb->levels[level][i], start, end)) {
+        changed = true;
+    } else {
+        endpos--;
+    }
+
+    if (level > 0 && changed) {
+        return hb_reset_between(hb, level - 1, pos, endpos);
+    }
+}
+
+void hbitmap_reset(HBitmap *hb, uint64_t start, uint64_t count)
+{
+    /* Compute range in the last layer.  */
+    uint64_t last = start + count - 1;
+
+    trace_hbitmap_reset(hb, start, count,
+                        start >> hb->granularity, last >> hb->granularity);
+
+    start >>= hb->granularity;
+    last >>= hb->granularity;
+
+    hb->count -= hb_count_between(hb, start, last);
+    hb_reset_between(hb, HBITMAP_LEVELS - 1, start, last);
+}
+
+bool hbitmap_get(HBitmap *hb, uint64_t item)
+{
+    /* Compute position and bit in the last layer.  */
+    uint64_t pos = item >> hb->granularity;
+    unsigned long bit = 1UL << (pos & (BITS_PER_LONG - 1));
+
+    return (hb->levels[HBITMAP_LEVELS - 1][pos >> BITS_PER_LEVEL] & bit) != 0;
+}
+
+void hbitmap_free(HBitmap *hb)
+{
+    int i;
+    for (i = HBITMAP_LEVELS; --i >= 0; ) {
+        g_free(hb->levels[i]);
+    }
+    g_free(hb);
+}
+
+HBitmap *hbitmap_alloc(uint64_t size, int granularity)
+{
+    HBitmap *hb = g_malloc0(sizeof (struct HBitmap));
+    int i;
+
+    size = (size + (1 << granularity) - 1) >> granularity;
+    assert(size <= ((uint64_t)1 << HBITMAP_LOG_MAX_SIZE));
+
+    hb->size = size;
+    hb->granularity = granularity;
+    for (i = HBITMAP_LEVELS; --i >= 0; ) {
+        size = MAX((size + BITS_PER_LONG - 1) >> BITS_PER_LEVEL, 1);
+        hb->levels[i] = g_malloc0(size * sizeof(unsigned long));
+    }
+
+    /* Add a sentinel in the level 0 bitmap.  We only use up to
+     * BITS_PER_LEVEL bits in level 0, so it's safe.
+     */
+    assert(size == 1);
+    hb->levels[0][0] |= 1UL << (BITS_PER_LONG - 1);
+    return hb;
+}
diff --git a/hbitmap.h b/hbitmap.h
new file mode 100644
index 0000000..2b717b0
--- /dev/null
+++ b/hbitmap.h
@@ -0,0 +1,51 @@
+/*
+ * Hierarchical Bitmap Data Type
+ *
+ * Copyright Red Hat, Inc., 2012
+ *
+ * Author: Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later.  See the COPYING file in the top-level directory.
+ */
+
+#ifndef HBITMAP_H
+#define HBITMAP_H 1
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "bitops.h"
+
+typedef struct HBitmap HBitmap;
+typedef struct HBitmapIter HBitmapIter;
+
+#define BITS_PER_LEVEL         (BITS_PER_LONG == 32 ? 5 : 6)
+
+/* For 32-bit, the largest that fits in a 4 GiB address space.
+ * For 64-bit, the number of sectors in 1 PiB.  Good luck, in
+ * either case... :)
+ */
+#define HBITMAP_LOG_MAX_SIZE   (BITS_PER_LONG == 32 ? 34 : 41)
+
+/* Leave an extra bit for a sentinel.  */
+#define HBITMAP_LEVELS         ((HBITMAP_LOG_MAX_SIZE / BITS_PER_LEVEL) + 1)
+
+struct HBitmapIter {
+    HBitmap *hb;
+    size_t pos;
+    int granularity;
+    unsigned long cur[HBITMAP_LEVELS];
+};
+
+int64_t hbitmap_iter_next(HBitmapIter *hbi);
+void hbitmap_iter_init(HBitmapIter *hbi, HBitmap *hb, uint64_t first);
+bool hbitmap_empty(HBitmap *hb);
+uint64_t hbitmap_count(HBitmap *hb);
+void hbitmap_set(HBitmap *hb, uint64_t start, uint64_t count);
+void hbitmap_reset(HBitmap *hb, uint64_t start, uint64_t count);
+bool hbitmap_get(HBitmap *hb, uint64_t item);
+void hbitmap_free(HBitmap *hb);
+HBitmap *hbitmap_alloc(uint64_t size, int granularity);
+
+#endif
diff --git a/tests/Makefile b/tests/Makefile
index d66ab19..3932b68 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -14,6 +14,7 @@ check-unit-y += tests/test-string-input-visitor$(EXESUF)
 check-unit-y += tests/test-string-output-visitor$(EXESUF)
 check-unit-y += tests/test-coroutine$(EXESUF)
 check-unit-y += tests/test-visitor-serialization$(EXESUF)
+check-unit-y += tests/test-hbitmap$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -47,6 +48,7 @@ tests/check-qlist$(EXESUF): tests/check-qlist.o qlist.o qint.o $(tools-obj-y)
 tests/check-qfloat$(EXESUF): tests/check-qfloat.o qfloat.o $(tools-obj-y)
 tests/check-qjson$(EXESUF): tests/check-qjson.o $(qobject-obj-y) $(tools-obj-y)
 tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(coroutine-obj-y) $(tools-obj-y)
+tests/test-hbitmap$(EXESUF): tests/test-hbitmap.o hbitmap.o $(trace-obj-y)
 
 tests/test-qapi-types.c tests/test-qapi-types.h :\
 $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py
diff --git a/tests/test-hbitmap.c b/tests/test-hbitmap.c
new file mode 100644
index 0000000..450fba3
--- /dev/null
+++ b/tests/test-hbitmap.c
@@ -0,0 +1,369 @@
+/*
+ * Hierarchical bitmap unit-tests.
+ *
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * Author: Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <glib.h>
+#include <stdarg.h>
+#include "hbitmap.h"
+
+#define LOG_BITS_PER_LONG          (BITS_PER_LONG == 32 ? 5 : 6)
+
+#define L1                         BITS_PER_LONG
+#define L2                         (BITS_PER_LONG * L1)
+#define L3                         (BITS_PER_LONG * L2)
+
+typedef struct TestHBitmapData {
+    HBitmap       *hb;
+    unsigned long *bits;
+    size_t         size;
+    int            granularity;
+} TestHBitmapData;
+
+
+/* Check that the HBitmap and the shadow bitmap contain the same data,
+ * ignoring the same "first" bits.
+ */
+static void hbitmap_test_check(TestHBitmapData *data,
+                               uint64_t first)
+{
+    uint64_t count = 0;
+    size_t pos;
+    int bit;
+    HBitmapIter hbi;
+    int64_t next;
+
+    hbitmap_iter_init(&hbi, data->hb, first);
+
+    for (;;) {
+        next = hbitmap_iter_next(&hbi);
+        if (next < 0) {
+            next = data->size;
+        }
+
+        while (first < next) {
+            pos = first >> LOG_BITS_PER_LONG;
+            bit = first & (BITS_PER_LONG - 1);
+            first++;
+            g_assert_cmpint(data->bits[pos] & (1UL << bit), ==, 0);
+        }
+
+        if (next == data->size) {
+            break;
+        }
+
+        pos = first >> LOG_BITS_PER_LONG;
+        bit = first & (BITS_PER_LONG - 1);
+        first++;
+        count++;
+        g_assert_cmpint(data->bits[pos] & (1UL << bit), !=, 0);
+    }
+
+    if (first == 0) {
+        g_assert_cmpint(count * data->granularity, ==, hbitmap_count(data->hb));
+    }
+}
+
+/* This is provided instead of a test setup function so that the sizes
+   are kept in the test functions (and not in main()) */
+static void hbitmap_test_init(TestHBitmapData *data,
+                              uint64_t size, int granularity)
+{
+    size_t n;
+    data->hb = hbitmap_alloc(size, granularity);
+
+    n = (size + BITS_PER_LONG - 1) / BITS_PER_LONG;
+    if (n == 0) {
+        n = 1;
+    }
+    data->bits = g_new0(unsigned long, n);
+    data->size = size;
+    data->granularity = granularity;
+    hbitmap_test_check(data, 0);
+}
+
+static void hbitmap_test_teardown(TestHBitmapData *data,
+                                  const void *unused)
+{
+    if (data->hb) {
+        hbitmap_free(data->hb);
+        data->hb = NULL;
+    }
+    if (data->bits) {
+        g_free(data->bits);
+        data->bits = NULL;
+    }
+}
+
+/* Set a range in the HBitmap and in the shadow "simple" bitmap.
+ * The two bitmaps are then tested against each other.
+ */
+static void hbitmap_test_set(TestHBitmapData *data,
+                             uint64_t first, uint64_t count)
+{
+    hbitmap_set(data->hb, first, count);
+    while (count-- != 0) {
+        size_t pos = first >> LOG_BITS_PER_LONG;
+        int bit = first & (BITS_PER_LONG - 1);
+        first++;
+
+        data->bits[pos] |= 1UL << bit;
+    }
+
+    if (data->granularity == 0) {
+        hbitmap_test_check(data, 0);
+    }
+}
+
+/* Reset a range in the HBitmap and in the shadow "simple" bitmap.
+ */
+static void hbitmap_test_reset(TestHBitmapData *data,
+                               uint64_t first, uint64_t count)
+{
+    hbitmap_reset(data->hb, first, count);
+    while (count-- != 0) {
+        size_t pos = first >> LOG_BITS_PER_LONG;
+        int bit = first & (BITS_PER_LONG - 1);
+        first++;
+
+        data->bits[pos] &= ~(1UL << bit);
+    }
+
+    if (data->granularity == 0) {
+        hbitmap_test_check(data, 0);
+    }
+}
+
+static void hbitmap_test_check_get(TestHBitmapData *data)
+{
+    uint64_t count = 0;
+    uint64_t i;
+
+    for (i = 0; i < data->size; i++) {
+        size_t pos = i >> LOG_BITS_PER_LONG;
+        int bit = i & (BITS_PER_LONG - 1);
+        unsigned long val = data->bits[pos] & (1UL << bit);
+        count += hbitmap_get(data->hb, i);
+        g_assert_cmpint(hbitmap_get(data->hb, i), ==, val != 0);
+    }
+    g_assert_cmpint(count, ==, hbitmap_count(data->hb));
+}
+
+static void test_hbitmap_zero(TestHBitmapData *data,
+                               const void *unused)
+{
+    hbitmap_test_init(data, 0, 0);
+}
+
+static void test_hbitmap_unaligned(TestHBitmapData *data,
+                                   const void *unused)
+{
+    hbitmap_test_init(data, L3 + 23, 0);
+    hbitmap_test_set(data, 0, 1);
+    hbitmap_test_set(data, L3 + 22, 1);
+}
+
+static void test_hbitmap_iter_empty(TestHBitmapData *data,
+                                    const void *unused)
+{
+    hbitmap_test_init(data, L1, 0);
+}
+
+static void test_hbitmap_iter_partial(TestHBitmapData *data,
+                                      const void *unused)
+{
+    hbitmap_test_init(data, L3, 0);
+    hbitmap_test_set(data, 0, L3);
+    hbitmap_test_check(data, 1);
+    hbitmap_test_check(data, L1 - 1);
+    hbitmap_test_check(data, L1);
+    hbitmap_test_check(data, L1 * 2 - 1);
+    hbitmap_test_check(data, L2 - 1);
+    hbitmap_test_check(data, L2);
+    hbitmap_test_check(data, L2 + 1);
+    hbitmap_test_check(data, L2 + L1);
+    hbitmap_test_check(data, L2 + L1 * 2 - 1);
+    hbitmap_test_check(data, L2 * 2 - 1);
+    hbitmap_test_check(data, L2 * 2);
+    hbitmap_test_check(data, L2 * 2 + 1);
+    hbitmap_test_check(data, L2 * 2 + L1);
+    hbitmap_test_check(data, L2 * 2 + L1 * 2 - 1);
+    hbitmap_test_check(data, L3 / 2);
+}
+
+static void test_hbitmap_iter_past(TestHBitmapData *data,
+                                    const void *unused)
+{
+    hbitmap_test_init(data, L3, 0);
+    hbitmap_test_set(data, 0, L3);
+    hbitmap_test_check(data, L3);
+}
+
+static void test_hbitmap_set_all(TestHBitmapData *data,
+                                 const void *unused)
+{
+    hbitmap_test_init(data, L3, 0);
+    hbitmap_test_set(data, 0, L3);
+}
+
+static void test_hbitmap_get_all(TestHBitmapData *data,
+                                 const void *unused)
+{
+    hbitmap_test_init(data, L3, 0);
+    hbitmap_test_set(data, 0, L3);
+    hbitmap_test_check_get(data);
+}
+
+static void test_hbitmap_get_some(TestHBitmapData *data,
+                                  const void *unused)
+{
+    hbitmap_test_init(data, 2 * L2, 0);
+    hbitmap_test_set(data, 10, 1);
+    hbitmap_test_check_get(data);
+    hbitmap_test_set(data, L1 - 1, 1);
+    hbitmap_test_check_get(data);
+    hbitmap_test_set(data, L1, 1);
+    hbitmap_test_check_get(data);
+    hbitmap_test_set(data, L2 - 1, 1);
+    hbitmap_test_check_get(data);
+    hbitmap_test_set(data, L2, 1);
+    hbitmap_test_check_get(data);
+}
+
+static void test_hbitmap_set_one(TestHBitmapData *data,
+                                 const void *unused)
+{
+    hbitmap_test_init(data, 2 * L2, 0);
+    hbitmap_test_set(data, 10, 1);
+    hbitmap_test_set(data, L1 - 1, 1);
+    hbitmap_test_set(data, L1, 1);
+    hbitmap_test_set(data, L2 - 1, 1);
+    hbitmap_test_set(data, L2, 1);
+}
+
+static void test_hbitmap_set_two_elem(TestHBitmapData *data,
+                                      const void *unused)
+{
+    hbitmap_test_init(data, 2 * L2, 0);
+    hbitmap_test_set(data, L1 - 1, 2);
+    hbitmap_test_set(data, L1 * 2 - 1, 4);
+    hbitmap_test_set(data, L1 * 4, L1 + 1);
+    hbitmap_test_set(data, L1 * 8 - 1, L1 + 1);
+    hbitmap_test_set(data, L2 - 1, 2);
+    hbitmap_test_set(data, L2 + L1 - 1, 8);
+    hbitmap_test_set(data, L2 + L1 * 4, L1 + 1);
+    hbitmap_test_set(data, L2 + L1 * 8 - 1, L1 + 1);
+}
+
+static void test_hbitmap_set(TestHBitmapData *data,
+                             const void *unused)
+{
+    hbitmap_test_init(data, L3 * 2, 0);
+    hbitmap_test_set(data, L1 - 1, L1 + 2);
+    hbitmap_test_set(data, L1 * 3 - 1, L1 + 2);
+    hbitmap_test_set(data, L1 * 5, L1 * 2 + 1);
+    hbitmap_test_set(data, L1 * 8 - 1, L1 * 2 + 1);
+    hbitmap_test_set(data, L2 - 1, L1 + 2);
+    hbitmap_test_set(data, L2 + L1 * 2 - 1, L1 + 2);
+    hbitmap_test_set(data, L2 + L1 * 4, L1 * 2 + 1);
+    hbitmap_test_set(data, L2 + L1 * 7 - 1, L1 * 2 + 1);
+    hbitmap_test_set(data, L2 * 2 - 1, L3 * 2 - L2 * 2);
+}
+
+static void test_hbitmap_set_overlap(TestHBitmapData *data,
+                                     const void *unused)
+{
+    hbitmap_test_init(data, L3 * 2, 0);
+    hbitmap_test_set(data, L1 - 1, L1 + 2);
+    hbitmap_test_set(data, L1 * 2 - 1, L1 * 2 + 2);
+    hbitmap_test_set(data, 0, L1 * 3);
+    hbitmap_test_set(data, L1 * 8 - 1, L2);
+    hbitmap_test_set(data, L2, L1);
+    hbitmap_test_set(data, L2 - L1 - 1, L1 * 8 + 2);
+    hbitmap_test_set(data, L2, L3 - L2 + 1);
+    hbitmap_test_set(data, L3 - L1, L1 * 3);
+    hbitmap_test_set(data, L3 - 1, 3);
+    hbitmap_test_set(data, L3 - 1, L2);
+}
+
+static void test_hbitmap_reset_empty(TestHBitmapData *data,
+                                     const void *unused)
+{
+    hbitmap_test_init(data, L3, 0);
+    hbitmap_test_reset(data, 0, L3);
+}
+
+static void test_hbitmap_reset(TestHBitmapData *data,
+                               const void *unused)
+{
+    hbitmap_test_init(data, L3 * 2, 0);
+    hbitmap_test_set(data, L1 - 1, L1 + 2);
+    hbitmap_test_reset(data, L1 * 2 - 1, L1 * 2 + 2);
+    hbitmap_test_set(data, 0, L1 * 3);
+    hbitmap_test_reset(data, L1 * 8 - 1, L2);
+    hbitmap_test_set(data, L2, L1);
+    hbitmap_test_reset(data, L2 - L1 - 1, L1 * 8 + 2);
+    hbitmap_test_set(data, L2, L3 - L2 + 1);
+    hbitmap_test_reset(data, L3 - L1, L1 * 3);
+    hbitmap_test_set(data, L3 - 1, 3);
+    hbitmap_test_reset(data, L3 - 1, L2);
+    hbitmap_test_set(data, 0, L3 * 2);
+    hbitmap_test_reset(data, 0, L1);
+    hbitmap_test_reset(data, 0, L2);
+    hbitmap_test_reset(data, L3, L3);
+    hbitmap_test_set(data, L3 / 2, L3);
+}
+
+static void test_hbitmap_granularity(TestHBitmapData *data,
+                                     const void *unused)
+{
+    /* Note that hbitmap_test_check has to be invoked manually in this test.  */
+    hbitmap_test_init(data, L1, 1);
+    hbitmap_test_set(data, 0, 1);
+    hbitmap_test_check(data, 0);
+    hbitmap_test_set(data, 2, 1);
+    hbitmap_test_check(data, 0);
+    hbitmap_test_set(data, 0, 3);
+    g_assert_cmpint(hbitmap_count(data->hb), ==, 4);
+    hbitmap_test_reset(data, 0, 1);
+    g_assert_cmpint(hbitmap_count(data->hb), ==, 2);
+}
+
+static void hbitmap_test_add(const char *testpath,
+                                   TestHBitmapData *data,
+                                   void (*test_func)(TestHBitmapData *data, const void *user_data))
+{
+    g_test_add(testpath, TestHBitmapData, data, NULL, test_func,
+               hbitmap_test_teardown);
+}
+
+int main(int argc, char **argv)
+{
+    TestHBitmapData hbitmap_data;
+
+    g_test_init(&argc, &argv, NULL);
+    hbitmap_test_add("/hbitmap/size/0", &hbitmap_data, test_hbitmap_zero);
+    hbitmap_test_add("/hbitmap/size/unaligned", &hbitmap_data, test_hbitmap_unaligned);
+    hbitmap_test_add("/hbitmap/iter/empty", &hbitmap_data, test_hbitmap_iter_empty);
+    hbitmap_test_add("/hbitmap/iter/past", &hbitmap_data, test_hbitmap_iter_past);
+    hbitmap_test_add("/hbitmap/iter/partial", &hbitmap_data, test_hbitmap_iter_partial);
+    hbitmap_test_add("/hbitmap/get/all", &hbitmap_data, test_hbitmap_get_all);
+    hbitmap_test_add("/hbitmap/get/some", &hbitmap_data, test_hbitmap_get_some);
+    hbitmap_test_add("/hbitmap/set/all", &hbitmap_data, test_hbitmap_set_all);
+    hbitmap_test_add("/hbitmap/set/one", &hbitmap_data, test_hbitmap_set_one);
+    hbitmap_test_add("/hbitmap/set/two-elem", &hbitmap_data, test_hbitmap_set_two_elem);
+    hbitmap_test_add("/hbitmap/set/general", &hbitmap_data, test_hbitmap_set);
+    hbitmap_test_add("/hbitmap/set/overlap", &hbitmap_data, test_hbitmap_set_overlap);
+    hbitmap_test_add("/hbitmap/reset/empty", &hbitmap_data, test_hbitmap_reset_empty);
+    hbitmap_test_add("/hbitmap/reset/general", &hbitmap_data, test_hbitmap_reset);
+    hbitmap_test_add("/hbitmap/granularity", &hbitmap_data, test_hbitmap_granularity);
+    g_test_run();
+
+    return 0;
+}
diff --git a/trace-events b/trace-events
index 65db490..cda332e 100644
--- a/trace-events
+++ b/trace-events
@@ -848,3 +848,8 @@ qxl_render_blit_guest_primary_initialized(void) ""
 qxl_render_blit(int32_t stride, int32_t left, int32_t right, int32_t top, int32_t bottom) "stride=%d [%d, %d, %d, %d]"
 qxl_render_guest_primary_resized(int32_t width, int32_t height, int32_t stride, int32_t bytes_pp, int32_t bits_pp) "%dx%d, stride %d, bpp %d, depth %d"
 qxl_render_update_area_done(void *cookie) "%p"
+
+# hbitmap.c
+hbitmap_iter_next(void *hb, void *hbi, int64_t item, int64_t bit) "hb %p hbi %p item %"PRId64" bit %"PRId64
+hbitmap_reset(void *hb, uint64_t start, uint64_t count, uint64_t sbit, uint64_t ebit) "hb %p items %"PRIu64",%"PRIu64" bits %"PRIu64"..%"PRIu64
+hbitmap_set(void *hb, uint64_t start, uint64_t count, uint64_t sbit, uint64_t ebit) "hb %p items %"PRIu64",%"PRIu64" bits %"PRIu64"..%"PRIu64
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 31/36] block: implement dirty bitmap using HBitmap
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (29 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 30/36] add hierarchical bitmap data type and test cases Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 32/36] block: make round_to_clusters public Paolo Bonzini
                   ` (4 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

This actually uses the dirty bitmap in the block layer, and converts
mirroring to use an HBitmapIter.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 Makefile.objs  |    2 +-
 block.c        |   94 +++++++++-----------------------------------------------
 block.h        |    6 ++--
 block/mirror.c |   11 +++++--
 block_int.h    |    4 +--
 5 files changed, 31 insertions(+), 86 deletions(-)

diff --git a/Makefile.objs b/Makefile.objs
index cef9933..e5262ff 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -42,7 +42,7 @@ coroutine-obj-$(CONFIG_WIN32) += coroutine-win32.o
 
 block-obj-y = cutils.o cache-utils.o qemu-option.o module.o async.o
 block-obj-y += nbd.o block.o blockjob.o aio.o aes.o qemu-config.o
-block-obj-y += qemu-progress.o qemu-sockets.o
+block-obj-y += qemu-progress.o qemu-sockets.o hbitmap.o
 block-obj-y += $(coroutine-obj-y) $(qobject-obj-y) $(version-obj-y)
 block-obj-$(CONFIG_POSIX) += posix-aio-compat.o
 block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
diff --git a/block.c b/block.c
index 5b08c5b..a105f9e 100644
--- a/block.c
+++ b/block.c
@@ -1032,7 +1032,6 @@ static void bdrv_move_feature_fields(BlockDriverState *bs_dest,
     bs_dest->iostatus           = bs_src->iostatus;
 
     /* dirty bitmap */
-    bs_dest->dirty_count        = bs_src->dirty_count;
     bs_dest->dirty_bitmap       = bs_src->dirty_bitmap;
 
     /* job */
@@ -1661,36 +1660,6 @@ int bdrv_read(BlockDriverState *bs, int64_t sector_num,
     return bdrv_rw_co(bs, sector_num, buf, nb_sectors, false);
 }
 
-#define BITS_PER_LONG  (sizeof(unsigned long) * 8)
-
-static void set_dirty_bitmap(BlockDriverState *bs, int64_t sector_num,
-                             int nb_sectors, int dirty)
-{
-    int64_t start, end;
-    unsigned long val, idx, bit;
-
-    start = sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK;
-    end = (sector_num + nb_sectors - 1) / BDRV_SECTORS_PER_DIRTY_CHUNK;
-
-    for (; start <= end; start++) {
-        idx = start / BITS_PER_LONG;
-        bit = start % BITS_PER_LONG;
-        val = bs->dirty_bitmap[idx];
-        if (dirty) {
-            if (!(val & (1UL << bit))) {
-                bs->dirty_count++;
-                val |= 1UL << bit;
-            }
-        } else {
-            if (val & (1UL << bit)) {
-                bs->dirty_count--;
-                val &= ~(1UL << bit);
-            }
-        }
-        bs->dirty_bitmap[idx] = val;
-    }
-}
-
 /* Return < 0 if error. Important errors are:
   -EIO         generic I/O error (may happen for all errors)
   -ENOMEDIUM   No media inserted.
@@ -4063,18 +4032,15 @@ void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable)
 {
     int64_t bitmap_size;
 
-    bs->dirty_count = 0;
     if (enable) {
         if (!bs->dirty_bitmap) {
-            bitmap_size = (bdrv_getlength(bs) >> BDRV_SECTOR_BITS) +
-                    BDRV_SECTORS_PER_DIRTY_CHUNK * BITS_PER_LONG - 1;
-            bitmap_size /= BDRV_SECTORS_PER_DIRTY_CHUNK * BITS_PER_LONG;
-
-            bs->dirty_bitmap = g_new0(unsigned long, bitmap_size);
+            bitmap_size = (bdrv_getlength(bs) >> BDRV_SECTOR_BITS);
+            bs->dirty_bitmap = hbitmap_alloc(bitmap_size,
+                                             BDRV_LOG_SECTORS_PER_DIRTY_CHUNK);
         }
     } else {
         if (bs->dirty_bitmap) {
-            g_free(bs->dirty_bitmap);
+            hbitmap_free(bs->dirty_bitmap);
             bs->dirty_bitmap = NULL;
         }
     }
@@ -4082,67 +4048,37 @@ void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable)
 
 int bdrv_get_dirty(BlockDriverState *bs, int64_t sector)
 {
-    int64_t chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK;
-
-    if (bs->dirty_bitmap &&
-        (sector << BDRV_SECTOR_BITS) < bdrv_getlength(bs)) {
-        return !!(bs->dirty_bitmap[chunk / BITS_PER_LONG] &
-            (1UL << (chunk % BITS_PER_LONG)));
+    if (bs->dirty_bitmap) {
+        return hbitmap_get(bs->dirty_bitmap, sector);
     } else {
         return 0;
     }
 }
 
-int64_t bdrv_get_next_dirty(BlockDriverState *bs, int64_t sector)
+void bdrv_dirty_iter_init(BlockDriverState *bs, HBitmapIter *hbi)
 {
-    int64_t chunk;
-    int bit, elem;
-
-    /* Avoid an infinite loop.  */
-    assert(bs->dirty_count > 0);
-
-    sector = (sector | (BDRV_SECTORS_PER_DIRTY_CHUNK - 1)) + 1;
-    chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK;
-
-    QEMU_BUILD_BUG_ON(sizeof(bs->dirty_bitmap[0]) * 8 != BITS_PER_LONG);
-    elem = chunk / BITS_PER_LONG;
-    bit = chunk % BITS_PER_LONG;
-    for (;;) {
-        if (sector >= bs->total_sectors) {
-            sector = 0;
-            bit = elem = 0;
-        }
-        if (bit == 0 && bs->dirty_bitmap[elem] == 0) {
-            sector += BDRV_SECTORS_PER_DIRTY_CHUNK * BITS_PER_LONG;
-            elem++;
-        } else {
-            if (bs->dirty_bitmap[elem] & (1UL << bit)) {
-                return sector;
-            }
-            sector += BDRV_SECTORS_PER_DIRTY_CHUNK;
-            if (++bit == BITS_PER_LONG) {
-                bit = 0;
-                elem++;
-            }
-        }
-    }
+    hbitmap_iter_init(hbi, bs->dirty_bitmap, 0);
 }
 
 void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
                     int nr_sectors)
 {
-    set_dirty_bitmap(bs, cur_sector, nr_sectors, 1);
+    hbitmap_set(bs->dirty_bitmap, cur_sector, nr_sectors);
 }
 
 void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
                       int nr_sectors)
 {
-    set_dirty_bitmap(bs, cur_sector, nr_sectors, 0);
+    hbitmap_reset(bs->dirty_bitmap, cur_sector, nr_sectors);
 }
 
 int64_t bdrv_get_dirty_count(BlockDriverState *bs)
 {
-    return bs->dirty_count;
+    if (bs->dirty_bitmap) {
+        return hbitmap_count(bs->dirty_bitmap) >> BDRV_LOG_SECTORS_PER_DIRTY_CHUNK;
+    } else {
+        return 0;
+    }
 }
 
 void bdrv_set_in_use(BlockDriverState *bs, int in_use)
diff --git a/block.h b/block.h
index a4aea47..0d8f21c 100644
--- a/block.h
+++ b/block.h
@@ -353,13 +353,15 @@ int bdrv_img_create(const char *filename, const char *fmt,
 void bdrv_set_buffer_alignment(BlockDriverState *bs, int align);
 void *qemu_blockalign(BlockDriverState *bs, size_t size);
 
-#define BDRV_SECTORS_PER_DIRTY_CHUNK 2048
+#define BDRV_SECTORS_PER_DIRTY_CHUNK     (1 << BDRV_LOG_SECTORS_PER_DIRTY_CHUNK)
+#define BDRV_LOG_SECTORS_PER_DIRTY_CHUNK 11
 
+struct HBitmapIter;
 void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable);
 int bdrv_get_dirty(BlockDriverState *bs, int64_t sector);
 void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
 void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
-int64_t bdrv_get_next_dirty(BlockDriverState *bs, int64_t sector);
+void bdrv_dirty_iter_init(BlockDriverState *bs, struct HBitmapIter *hbi);
 int64_t bdrv_get_dirty_count(BlockDriverState *bs);
 
 void bdrv_enable_copy_on_read(BlockDriverState *bs);
diff --git a/block/mirror.c b/block/mirror.c
index 346a0eb..787b763 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -36,6 +36,7 @@ typedef struct MirrorBlockJob {
     bool synced;
     bool complete;
     int64_t sector_num;
+    HBitmapIter hbi;
     void *buf;
 } MirrorBlockJob;
 
@@ -49,8 +50,14 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s,
     int64_t end;
     struct iovec iov;
 
+    s->sector_num = hbitmap_iter_next(&s->hbi);
+    if (s->sector_num < 0) {
+        bdrv_dirty_iter_init(source, &s->hbi);
+        s->sector_num = hbitmap_iter_next(&s->hbi);
+        assert(s->sector_num >= 0);
+    }
+
     end = s->common.len >> BDRV_SECTOR_BITS;
-    s->sector_num = bdrv_get_next_dirty(source, s->sector_num);
     nb_sectors = MIN(BDRV_SECTORS_PER_DIRTY_CHUNK, end - s->sector_num);
     trace_mirror_one_iteration(s, s->sector_num);
     bdrv_reset_dirty(source, s->sector_num, BDRV_SECTORS_PER_DIRTY_CHUNK);
@@ -126,7 +133,7 @@ static void coroutine_fn mirror_run(void *opaque)
         block_job_completed(&s->common, ret);
     }
 
-    s->sector_num = -1;
+    bdrv_dirty_iter_init(bs, &s->hbi);
     for (;;) {
         uint64_t delay_ns;
         int64_t cnt;
diff --git a/block_int.h b/block_int.h
index 599eb37..6d36fe7 100644
--- a/block_int.h
+++ b/block_int.h
@@ -30,6 +30,7 @@
 #include "qemu-coroutine.h"
 #include "qemu-timer.h"
 #include "qapi-types.h"
+#include "hbitmap.h"
 #include "monitor.h"
 
 #define BLOCK_FLAG_ENCRYPT	1
@@ -259,8 +260,7 @@ struct BlockDriverState {
     bool iostatus_enabled;
     BlockDeviceIoStatus iostatus;
     char device_name[32];
-    unsigned long *dirty_bitmap;
-    int64_t dirty_count;
+    HBitmap *dirty_bitmap;
     int in_use; /* users other than guest access, eg. block migration */
     QTAILQ_ENTRY(BlockDriverState) list;
 
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 32/36] block: make round_to_clusters public
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (30 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 31/36] block: implement dirty bitmap using HBitmap Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 33/36] mirror: perform COW if the cluster size is bigger than the granularity Paolo Bonzini
                   ` (3 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

This is needed in the following patch.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block.c |   16 ++++++++--------
 block.h |    4 ++++
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/block.c b/block.c
index a105f9e..35ceed5 100644
--- a/block.c
+++ b/block.c
@@ -1456,10 +1456,10 @@ static void tracked_request_begin(BdrvTrackedRequest *req,
 /**
  * Round a region to cluster boundaries
  */
-static void round_to_clusters(BlockDriverState *bs,
-                              int64_t sector_num, int nb_sectors,
-                              int64_t *cluster_sector_num,
-                              int *cluster_nb_sectors)
+void bdrv_round_to_clusters(BlockDriverState *bs,
+                            int64_t sector_num, int nb_sectors,
+                            int64_t *cluster_sector_num,
+                            int *cluster_nb_sectors)
 {
     BlockDriverInfo bdi;
 
@@ -1501,8 +1501,8 @@ static void coroutine_fn wait_for_overlapping_requests(BlockDriverState *bs,
      * CoR read and write operations are atomic and guest writes cannot
      * interleave between them.
      */
-    round_to_clusters(bs, sector_num, nb_sectors,
-                      &cluster_sector_num, &cluster_nb_sectors);
+    bdrv_round_to_clusters(bs, sector_num, nb_sectors,
+                           &cluster_sector_num, &cluster_nb_sectors);
 
     do {
         retry = false;
@@ -1811,8 +1811,8 @@ static int coroutine_fn bdrv_co_do_copy_on_readv(BlockDriverState *bs,
     /* Cover entire cluster so no additional backing file I/O is required when
      * allocating cluster in the image file.
      */
-    round_to_clusters(bs, sector_num, nb_sectors,
-                      &cluster_sector_num, &cluster_nb_sectors);
+    bdrv_round_to_clusters(bs, sector_num, nb_sectors,
+                           &cluster_sector_num, &cluster_nb_sectors);
 
     trace_bdrv_co_do_copy_on_readv(bs, sector_num, nb_sectors,
                                    cluster_sector_num, cluster_nb_sectors);
diff --git a/block.h b/block.h
index 0d8f21c..a79f26b 100644
--- a/block.h
+++ b/block.h
@@ -312,6 +312,10 @@ int bdrv_get_flags(BlockDriverState *bs);
 int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num,
                           const uint8_t *buf, int nb_sectors);
 int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi);
+void bdrv_round_to_clusters(BlockDriverState *bs,
+                            int64_t sector_num, int nb_sectors,
+                            int64_t *cluster_sector_num,
+                            int *cluster_nb_sectors);
 
 const char *bdrv_get_encrypted_filename(BlockDriverState *bs);
 void bdrv_get_backing_filename(BlockDriverState *bs,
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 33/36] mirror: perform COW if the cluster size is bigger than the granularity
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (31 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 32/36] block: make round_to_clusters public Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 34/36] block: return count of dirty sectors, not chunks Paolo Bonzini
                   ` (2 subsequent siblings)
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

When mirroring runs, the backing files for the target are not yet ready.
However, this means that a copy-on-write operation on the target would
fill the missing sectors with zeros.  Avoid this by always copying a
whole cluster the first time it is touched.

The code keeps a bitmap of clusters that have already been allocated
by the mirroring job, and only does "manual" copy-on-write if the
chunk being copied is zero in the bitmap.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/mirror.c |   58 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 blockdev.c     |    2 --
 2 files changed, 50 insertions(+), 10 deletions(-)

diff --git a/block/mirror.c b/block/mirror.c
index 787b763..fcedd66 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -15,6 +15,7 @@
 #include "blockjob.h"
 #include "block_int.h"
 #include "qemu/ratelimit.h"
+#include "bitmap.h"
 
 enum {
     /*
@@ -36,6 +37,8 @@ typedef struct MirrorBlockJob {
     bool synced;
     bool complete;
     int64_t sector_num;
+    int64_t buf_size;
+    unsigned long *cow_bitmap;
     HBitmapIter hbi;
     void *buf;
 } MirrorBlockJob;
@@ -47,7 +50,7 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s,
     BlockDriverState *target = s->target;
     QEMUIOVector qiov;
     int ret, nb_sectors;
-    int64_t end;
+    int64_t end, sector_num, cluster_num;
     struct iovec iov;
 
     s->sector_num = hbitmap_iter_next(&s->hbi);
@@ -57,23 +60,41 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s,
         assert(s->sector_num >= 0);
     }
 
+    /* If we have no backing file yet in the destination, and the cluster size
+     * is very large, we need to do COW ourselves.  The first time a cluster is
+     * copied, copy it entirely.
+     *
+     * Because both BDRV_SECTORS_PER_DIRTY_CHUNK and the cluster size are
+     * powers of two, the number of sectors to copy cannot exceed one cluster.
+     */
+    sector_num = s->sector_num;
+    nb_sectors = BDRV_SECTORS_PER_DIRTY_CHUNK;
+    cluster_num = sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK;
+    if (s->cow_bitmap && !test_bit(cluster_num, s->cow_bitmap)) {
+        bdrv_round_to_clusters(s->target,
+                               sector_num, BDRV_SECTORS_PER_DIRTY_CHUNK,
+                               &sector_num, &nb_sectors);
+        bitmap_set(s->cow_bitmap, sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK,
+                   nb_sectors / BDRV_SECTORS_PER_DIRTY_CHUNK);
+    }
+
     end = s->common.len >> BDRV_SECTOR_BITS;
-    nb_sectors = MIN(BDRV_SECTORS_PER_DIRTY_CHUNK, end - s->sector_num);
-    trace_mirror_one_iteration(s, s->sector_num);
-    bdrv_reset_dirty(source, s->sector_num, BDRV_SECTORS_PER_DIRTY_CHUNK);
+    nb_sectors = MIN(nb_sectors, end - sector_num);
+    trace_mirror_one_iteration(s, sector_num);
+    bdrv_reset_dirty(source, sector_num, BDRV_SECTORS_PER_DIRTY_CHUNK);
 
     /* Copy the dirty cluster.  */
     iov.iov_base = s->buf;
     iov.iov_len  = nb_sectors * 512;
     qemu_iovec_init_external(&qiov, &iov, 1);
 
-    ret = bdrv_co_readv(source, s->sector_num, nb_sectors, &qiov);
+    ret = bdrv_co_readv(source, sector_num, nb_sectors, &qiov);
     if (ret < 0) {
         *p_action = block_job_error_action(&s->common, source,
                                            s->on_source_error, true, -ret);
         goto fail;
     }
-    ret = bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov);
+    ret = bdrv_co_writev(target, sector_num, nb_sectors, &qiov);
     if (ret < 0) {
         *p_action = block_job_error_action(&s->common, target,
                                            s->on_target_error, false, -ret);
@@ -84,7 +105,7 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s,
 
 fail:
     /* Try again later.  */
-    bdrv_set_dirty(source, s->sector_num, nb_sectors);
+    bdrv_set_dirty(source, sector_num, nb_sectors);
     return ret;
 }
 
@@ -107,7 +128,7 @@ static void coroutine_fn mirror_run(void *opaque)
     }
 
     end = s->common.len >> BDRV_SECTOR_BITS;
-    s->buf = qemu_blockalign(bs, BLOCK_SIZE);
+    s->buf = qemu_blockalign(bs, s->buf_size);
 
     if (s->mode == MIRROR_SYNC_MODE_FULL || s->mode == MIRROR_SYNC_MODE_TOP) {
         /* First part, loop on the sectors and initialize the dirty bitmap.  */
@@ -211,6 +232,7 @@ static void coroutine_fn mirror_run(void *opaque)
 
 immediate_exit:
     g_free(s->buf);
+    g_free(s->cow_bitmap);
     bdrv_set_dirty_tracking(bs, false);
     bdrv_iostatus_disable(s->target);
     if (s->synced && ret == 0) {
@@ -292,6 +314,9 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
                   void *opaque, Error **errp)
 {
     MirrorBlockJob *s;
+    BlockDriverInfo bdi;
+    char backing_filename[1024];
+    int64_t length;
 
     s = block_job_create(&mirror_job_type, bs, speed, cb, opaque, errp);
     if (!s) {
@@ -302,6 +327,23 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
     s->on_target_error = on_target_error;
     s->target = target;
     s->mode = mode;
+
+    /* If we have no backing file yet in the destination, we cannot let
+     * the destination do COW.  Instead, we copy sectors around the
+     * dirty data if needed.
+     */
+    s->buf_size = BLOCK_SIZE;
+    bdrv_get_backing_filename(s->target, backing_filename,
+                              sizeof(backing_filename));
+    if (backing_filename[0] && !s->target->backing_hd) {
+        bdrv_get_info(s->target, &bdi);
+        if (s->buf_size < bdi.cluster_size) {
+            s->buf_size = bdi.cluster_size;
+            length = (bdrv_getlength(bs) + BLOCK_SIZE - 1) / BLOCK_SIZE;
+            s->cow_bitmap = bitmap_new(length);
+        }
+    }
+
     bdrv_set_dirty_tracking(bs, true);
     bdrv_set_on_error(s->target, on_target_error, on_target_error);
     bdrv_iostatus_enable(s->target);
diff --git a/blockdev.c b/blockdev.c
index b46a86c..f940e8f 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -927,8 +927,6 @@ void qmp_drive_mirror(const char *device, const char *target,
         return;
     }
 
-    /* ### TODO check for cluster size vs. dirty bitmap granularity */
-
     target_bs = bdrv_new("");
     ret = bdrv_open(target_bs, target, flags | BDRV_O_NO_BACKING, drv);
 
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 34/36] block: return count of dirty sectors, not chunks
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (32 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 33/36] mirror: perform COW if the cluster size is bigger than the granularity Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 35/36] block: allow customizing the granularity of the dirty bitmap Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 36/36] mirror: allow customizing the granularity Paolo Bonzini
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block-migration.c |    2 +-
 block.c           |    2 +-
 block/mirror.c    |    2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/block-migration.c b/block-migration.c
index fd2ffff..6fdce1a 100644
--- a/block-migration.c
+++ b/block-migration.c
@@ -485,7 +485,7 @@ static int64_t get_remaining_dirty(void)
         dirty += bdrv_get_dirty_count(bmds->bs);
     }
 
-    return dirty * BLOCK_SIZE;
+    return dirty << BDRV_SECTOR_BITS;
 }
 
 static int is_stage2_completed(void)
diff --git a/block.c b/block.c
index 35ceed5..b1d653f 100644
--- a/block.c
+++ b/block.c
@@ -4075,7 +4075,7 @@ void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
 int64_t bdrv_get_dirty_count(BlockDriverState *bs)
 {
     if (bs->dirty_bitmap) {
-        return hbitmap_count(bs->dirty_bitmap) >> BDRV_LOG_SECTORS_PER_DIRTY_CHUNK;
+        return hbitmap_count(bs->dirty_bitmap);
     } else {
         return 0;
     }
diff --git a/block/mirror.c b/block/mirror.c
index fcedd66..cc9f1fb 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -212,7 +212,7 @@ static void coroutine_fn mirror_run(void *opaque)
             }
         } else {
             /* Publish progress */
-            s->common.offset = end * BDRV_SECTOR_SIZE - cnt * BLOCK_SIZE;
+            s->common.offset = (end - cnt) * BDRV_SECTOR_SIZE;
 
             if (s->common.speed) {
                 delay_ns = ratelimit_calculate_delay(&s->limit, BDRV_SECTORS_PER_DIRTY_CHUNK);
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 35/36] block: allow customizing the granularity of the dirty bitmap
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (33 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 34/36] block: return count of dirty sectors, not chunks Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 36/36] mirror: allow customizing the granularity Paolo Bonzini
  35 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block-migration.c |    6 ++++--
 block.c           |   14 +++++++-------
 block.h           |    5 +----
 block/mirror.c    |   14 ++++----------
 4 files changed, 16 insertions(+), 23 deletions(-)

diff --git a/block-migration.c b/block-migration.c
index 6fdce1a..3b05c77 100644
--- a/block-migration.c
+++ b/block-migration.c
@@ -23,7 +23,8 @@
 #include "blockdev.h"
 #include <assert.h>
 
-#define BLOCK_SIZE (BDRV_SECTORS_PER_DIRTY_CHUNK << BDRV_SECTOR_BITS)
+#define BLOCK_SIZE                       (1 << 20)
+#define BDRV_SECTORS_PER_DIRTY_CHUNK     (BLOCK_SIZE >> BDRV_SECTOR_BITS)
 
 #define BLK_MIG_FLAG_DEVICE_BLOCK       0x01
 #define BLK_MIG_FLAG_EOS                0x02
@@ -264,7 +265,8 @@ static void set_dirty_tracking(int enable)
     BlkMigDevState *bmds;
 
     QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
-        bdrv_set_dirty_tracking(bmds->bs, enable);
+        bdrv_set_dirty_tracking(bmds->bs,
+                                enable ? BDRV_SECTORS_PER_DIRTY_CHUNK : 0);
     }
 }
 
diff --git a/block.c b/block.c
index b1d653f..e25b18a 100644
--- a/block.c
+++ b/block.c
@@ -4028,16 +4028,16 @@ void *qemu_blockalign(BlockDriverState *bs, size_t size)
     return qemu_memalign((bs && bs->buffer_alignment) ? bs->buffer_alignment : 512, size);
 }
 
-void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable)
+void bdrv_set_dirty_tracking(BlockDriverState *bs, int granularity)
 {
     int64_t bitmap_size;
 
-    if (enable) {
-        if (!bs->dirty_bitmap) {
-            bitmap_size = (bdrv_getlength(bs) >> BDRV_SECTOR_BITS);
-            bs->dirty_bitmap = hbitmap_alloc(bitmap_size,
-                                             BDRV_LOG_SECTORS_PER_DIRTY_CHUNK);
-        }
+    assert((granularity & (granularity - 1)) == 0);
+
+    if (granularity) {
+        assert(!bs->dirty_bitmap);
+        bitmap_size = (bdrv_getlength(bs) >> BDRV_SECTOR_BITS);
+        bs->dirty_bitmap = hbitmap_alloc(bitmap_size, ffs(granularity) - 1);
     } else {
         if (bs->dirty_bitmap) {
             hbitmap_free(bs->dirty_bitmap);
diff --git a/block.h b/block.h
index a79f26b..546fc02 100644
--- a/block.h
+++ b/block.h
@@ -357,11 +357,8 @@ int bdrv_img_create(const char *filename, const char *fmt,
 void bdrv_set_buffer_alignment(BlockDriverState *bs, int align);
 void *qemu_blockalign(BlockDriverState *bs, size_t size);
 
-#define BDRV_SECTORS_PER_DIRTY_CHUNK     (1 << BDRV_LOG_SECTORS_PER_DIRTY_CHUNK)
-#define BDRV_LOG_SECTORS_PER_DIRTY_CHUNK 11
-
 struct HBitmapIter;
-void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable);
+void bdrv_set_dirty_tracking(BlockDriverState *bs, int granularity);
 int bdrv_get_dirty(BlockDriverState *bs, int64_t sector);
 void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
 void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
diff --git a/block/mirror.c b/block/mirror.c
index cc9f1fb..e09beaf 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -17,14 +17,8 @@
 #include "qemu/ratelimit.h"
 #include "bitmap.h"
 
-enum {
-    /*
-     * Size of data buffer for populating the image file.  This should be large
-     * enough to process multiple clusters in a single call, so that populating
-     * contiguous regions of the image is efficient.
-     */
-    BLOCK_SIZE = 512 * BDRV_SECTORS_PER_DIRTY_CHUNK, /* in bytes */
-};
+#define BLOCK_SIZE                       (1 << 20)
+#define BDRV_SECTORS_PER_DIRTY_CHUNK     (BLOCK_SIZE >> BDRV_SECTOR_BITS)
 
 #define SLICE_TIME 100000000ULL /* ns */
 
@@ -233,7 +227,7 @@ static void coroutine_fn mirror_run(void *opaque)
 immediate_exit:
     g_free(s->buf);
     g_free(s->cow_bitmap);
-    bdrv_set_dirty_tracking(bs, false);
+    bdrv_set_dirty_tracking(bs, 0);
     bdrv_iostatus_disable(s->target);
     if (s->synced && ret == 0) {
         ret = bdrv_flush(s->target);
@@ -344,7 +338,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
         }
     }
 
-    bdrv_set_dirty_tracking(bs, true);
+    bdrv_set_dirty_tracking(bs, BDRV_SECTORS_PER_DIRTY_CHUNK);
     bdrv_set_on_error(s->target, on_target_error, on_target_error);
     bdrv_iostatus_enable(s->target);
     s->common.co = qemu_coroutine_create(mirror_run);
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* [Qemu-devel] [RFC PATCH 36/36] mirror: allow customizing the granularity
  2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
                   ` (34 preceding siblings ...)
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 35/36] block: allow customizing the granularity of the dirty bitmap Paolo Bonzini
@ 2012-06-15 15:05 ` Paolo Bonzini
  2012-06-15 23:24   ` Eric Blake
  35 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 15:05 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, stefanha, lcapitulino

The desired granularity may be very different depending on the kind of operation
(e.g. continous replication vs. collapse-to-raw) and whether the VM is expected
to perform lots of I/O while mirroring is in progress.  Allow the user to customize
it.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/mirror.c   |   38 +++++++++++++++++++-------------------
 block_int.h      |    3 ++-
 blockdev.c       |    7 ++++++-
 hmp.c            |    2 +-
 qapi-schema.json |    5 ++++-
 qmp-commands.hx  |    4 +++-
 6 files changed, 35 insertions(+), 24 deletions(-)

diff --git a/block/mirror.c b/block/mirror.c
index e09beaf..4e6aa81 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -17,9 +17,6 @@
 #include "qemu/ratelimit.h"
 #include "bitmap.h"
 
-#define BLOCK_SIZE                       (1 << 20)
-#define BDRV_SECTORS_PER_DIRTY_CHUNK     (BLOCK_SIZE >> BDRV_SECTOR_BITS)
-
 #define SLICE_TIME 100000000ULL /* ns */
 
 typedef struct MirrorBlockJob {
@@ -31,6 +28,7 @@ typedef struct MirrorBlockJob {
     bool synced;
     bool complete;
     int64_t sector_num;
+    int64_t granularity;
     int64_t buf_size;
     unsigned long *cow_bitmap;
     HBitmapIter hbi;
@@ -43,7 +41,7 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s,
     BlockDriverState *source = s->common.bs;
     BlockDriverState *target = s->target;
     QEMUIOVector qiov;
-    int ret, nb_sectors;
+    int ret, nb_sectors, nb_sectors_chunk;
     int64_t end, sector_num, cluster_num;
     struct iovec iov;
 
@@ -58,24 +56,24 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s,
      * is very large, we need to do COW ourselves.  The first time a cluster is
      * copied, copy it entirely.
      *
-     * Because both BDRV_SECTORS_PER_DIRTY_CHUNK and the cluster size are
-     * powers of two, the number of sectors to copy cannot exceed one cluster.
+     * Because both the granularity and the cluster size are powers of two, the
+     * number of sectors to copy cannot exceed one cluster.
      */
     sector_num = s->sector_num;
-    nb_sectors = BDRV_SECTORS_PER_DIRTY_CHUNK;
-    cluster_num = sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK;
+    nb_sectors_chunk = nb_sectors = s->granularity >> BDRV_SECTOR_BITS;
+    cluster_num = sector_num / nb_sectors_chunk;
     if (s->cow_bitmap && !test_bit(cluster_num, s->cow_bitmap)) {
         bdrv_round_to_clusters(s->target,
-                               sector_num, BDRV_SECTORS_PER_DIRTY_CHUNK,
+                               sector_num, nb_sectors_chunk,
                                &sector_num, &nb_sectors);
-        bitmap_set(s->cow_bitmap, sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK,
-                   nb_sectors / BDRV_SECTORS_PER_DIRTY_CHUNK);
+        bitmap_set(s->cow_bitmap, sector_num / nb_sectors_chunk,
+                   nb_sectors / nb_sectors_chunk);
     }
 
     end = s->common.len >> BDRV_SECTOR_BITS;
     nb_sectors = MIN(nb_sectors, end - sector_num);
     trace_mirror_one_iteration(s, sector_num);
-    bdrv_reset_dirty(source, sector_num, BDRV_SECTORS_PER_DIRTY_CHUNK);
+    bdrv_reset_dirty(source, sector_num, nb_sectors);
 
     /* Copy the dirty cluster.  */
     iov.iov_base = s->buf;
@@ -107,7 +105,7 @@ static void coroutine_fn mirror_run(void *opaque)
 {
     MirrorBlockJob *s = opaque;
     BlockDriverState *bs = s->common.bs;
-    int64_t sector_num, end;
+    int64_t sector_num, end, nb_sectors_chunk;
     int ret = 0;
     int n;
 
@@ -123,13 +121,14 @@ static void coroutine_fn mirror_run(void *opaque)
 
     end = s->common.len >> BDRV_SECTOR_BITS;
     s->buf = qemu_blockalign(bs, s->buf_size);
+    nb_sectors_chunk = s->granularity >> BDRV_SECTOR_BITS;
 
     if (s->mode == MIRROR_SYNC_MODE_FULL || s->mode == MIRROR_SYNC_MODE_TOP) {
         /* First part, loop on the sectors and initialize the dirty bitmap.  */
         BlockDriverState *base;
         base = s->mode == MIRROR_SYNC_MODE_FULL ? NULL : bs->backing_hd;
         for (sector_num = 0; sector_num < end; ) {
-            int64_t next = (sector_num | (BDRV_SECTORS_PER_DIRTY_CHUNK - 1)) + 1;
+            int64_t next = (sector_num | (nb_sectors_chunk - 1)) + 1;
             ret = bdrv_co_is_allocated_above(bs, base,
                                              sector_num, next - sector_num, &n);
 
@@ -209,7 +208,7 @@ static void coroutine_fn mirror_run(void *opaque)
             s->common.offset = (end - cnt) * BDRV_SECTOR_SIZE;
 
             if (s->common.speed) {
-                delay_ns = ratelimit_calculate_delay(&s->limit, BDRV_SECTORS_PER_DIRTY_CHUNK);
+                delay_ns = ratelimit_calculate_delay(&s->limit, nb_sectors_chunk);
             } else {
                 delay_ns = 0;
             }
@@ -301,7 +300,7 @@ static BlockJobType mirror_job_type = {
 };
 
 void mirror_start(BlockDriverState *bs, BlockDriverState *target,
-                  int64_t speed, MirrorSyncMode mode,
+                  int64_t speed, int64_t granularity, MirrorSyncMode mode,
                   BlockdevOnError on_source_error,
                   BlockdevOnError on_target_error,
                   BlockDriverCompletionFunc *cb,
@@ -326,19 +325,20 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
      * the destination do COW.  Instead, we copy sectors around the
      * dirty data if needed.
      */
-    s->buf_size = BLOCK_SIZE;
+    s->granularity = granularity;
+    s->buf_size = granularity;
     bdrv_get_backing_filename(s->target, backing_filename,
                               sizeof(backing_filename));
     if (backing_filename[0] && !s->target->backing_hd) {
         bdrv_get_info(s->target, &bdi);
         if (s->buf_size < bdi.cluster_size) {
             s->buf_size = bdi.cluster_size;
-            length = (bdrv_getlength(bs) + BLOCK_SIZE - 1) / BLOCK_SIZE;
+            length = (bdrv_getlength(bs) + granularity - 1) / granularity;
             s->cow_bitmap = bitmap_new(length);
         }
     }
 
-    bdrv_set_dirty_tracking(bs, BDRV_SECTORS_PER_DIRTY_CHUNK);
+    bdrv_set_dirty_tracking(bs, granularity >> BDRV_SECTOR_BITS);
     bdrv_set_on_error(s->target, on_target_error, on_target_error);
     bdrv_iostatus_enable(s->target);
     s->common.co = qemu_coroutine_create(mirror_run);
diff --git a/block_int.h b/block_int.h
index 6d36fe7..8bbe479 100644
--- a/block_int.h
+++ b/block_int.h
@@ -311,6 +311,7 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
  * @bs: Block device to operate on.
  * @target: Block device to write to.
  * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
+ * @granularity: The chosen granularity for the dirty bitmap.
  * @mode: Whether to collapse all images in the chain to the target.
  * @on_source_error: The action to take upon error reading from the source.
  * @on_target_error: The action to take upon error writing to the target.
@@ -324,7 +325,7 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
  * @bs will be switched to read from @target.
  */
 void mirror_start(BlockDriverState *bs, BlockDriverState *target,
-                  int64_t speed, MirrorSyncMode mode,
+                  int64_t speed, int64_t granularity, MirrorSyncMode mode,
                   BlockdevOnError on_source_error,
                   BlockdevOnError on_target_error,
                   BlockDriverCompletionFunc *cb,
diff --git a/blockdev.c b/blockdev.c
index f940e8f..a1b2b1b 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -836,6 +836,7 @@ void qmp_drive_mirror(const char *device, const char *target,
                       enum MirrorSyncMode sync,
                       bool has_mode, enum NewImageMode mode,
                       bool has_speed, int64_t speed,
+                      bool has_granularity, int64_t granularity,
                       bool has_on_source_error, BlockdevOnError on_source_error,
                       bool has_on_target_error, BlockdevOnError on_target_error,
                       Error **errp)
@@ -858,6 +859,9 @@ void qmp_drive_mirror(const char *device, const char *target,
     if (!has_mode) {
         mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
     }
+    if (!has_granularity) {
+        granularity = 65536;
+    }
 
     bs = bdrv_find(device);
     if (!bs) {
@@ -936,7 +940,8 @@ void qmp_drive_mirror(const char *device, const char *target,
         return;
     }
 
-    mirror_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
+    mirror_start(bs, target_bs, speed, granularity, sync,
+                 on_source_error, on_target_error,
                  block_job_cb, bs, &local_err);
     if (local_err != NULL) {
         bdrv_delete(target_bs);
diff --git a/hmp.c b/hmp.c
index ef0b87f..f01d608 100644
--- a/hmp.c
+++ b/hmp.c
@@ -717,7 +717,7 @@ void hmp_drive_mirror(Monitor *mon, const QDict *qdict)
 
     qmp_drive_mirror(device, filename, !!format, format,
                      full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
-                     true, mode, false, 0,
+                     true, mode, false, 0, false, 0,
                      false, 0, false, 0, &errp);
     hmp_handle_error(mon, &errp);
 }
diff --git a/qapi-schema.json b/qapi-schema.json
index bbcfa0e..b579581 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1383,6 +1383,8 @@
 #        (all the disk, only the sectors allocated in the topmost image, or
 #        only new I/O).
 #
+# @granularity: #optional granularity of the dirty bitmap, default is 64K.
+#
 # @on_source_error: #optional the action to take on an error on the source
 #
 # @on_target_error: #optional the action to take on an error on the target
@@ -1397,7 +1399,8 @@
 { 'command': 'drive-mirror',
   'data': { 'device': 'str', 'target': 'str', '*format': 'str',
             'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
-            '*speed': 'int', '*on_source_error': 'BlockdevOnError',
+            '*speed': 'int', '*granularity': 'int',
+            '*on_source_error': 'BlockdevOnError',
             '*on_target_error': 'BlockdevOnError' } }
 
 ##
diff --git a/qmp-commands.hx b/qmp-commands.hx
index f1b0f90..40ef5ab 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -839,7 +839,8 @@ EQMP
     {
         .name       = "drive-mirror",
         .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?,"
-                      "on_source_error:s?,on_target_error:s?",
+                      "on_source_error:s?,on_target_error:s?,"
+                      "granularity:i?",
         .mhandler.cmd_new = qmp_marshal_input_drive_mirror,
     },
 
@@ -862,6 +863,7 @@ Arguments:
   file/device (NewImageMode, optional, default 'absolute-paths')
 - "speed": maximum speed of the streaming job, in bytes per second
   (json-int)
+- "granularity": granularity of the dirty bitmap (json-int, default 64k)
 - "sync": what parts of the disk image should be copied to the destination;
   possibilities include "full" for all the disk, "top" for only the sectors
   allocated in the topmost image, or "none" to only replicate new I/O
-- 
1.7.10.2

^ permalink raw reply related	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands Paolo Bonzini
@ 2012-06-15 16:45   ` Eric Blake
  2012-07-11 16:00     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 16:45 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 1675 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> Talk about background operations in general, rather than specifically
> about streaming.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  hmp-commands.hx  |    2 +-
>  qapi-schema.json |   17 ++++++++---------
>  2 files changed, 9 insertions(+), 10 deletions(-)
> 
> diff --git a/hmp-commands.hx b/hmp-commands.hx
> index f5d9d91..26a4d8d 100644
> --- a/hmp-commands.hx
> +++ b/hmp-commands.hx
> @@ -101,7 +101,7 @@ ETEXI
>          .name       = "block_job_cancel",
>          .args_type  = "device:B",
>          .params     = "device",
> -        .help       = "stop an active block streaming operation",
> +        .help       = "stop an active background block operation",

Reviewed-by: Eric Blake <eblake@redhat.com>

>  #
> -# The image file retains its backing file unless the streaming operation happens
> -# to complete just as it is being cancelled.
> -#
> -# A new block streaming operation can be started at a later time to finish
> -# copying all data from the backing file.
> +# For streaming, the image file retains its backing file unless the streaming
> +# operation happens to complete just as it is being cancelled.  A new streaming

Pre-existing, but do we have any policy on US (canceled) vs. UK
(cancelled) spelling?  Obviously, our API spelling is locked into UK
spelling, but if the docs generally prefer US spelling, do we need to
adjust things here?  But any such adjustments should be separate
commits, so no impact to my reviewed-by.

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 02/36] qerror/block: introduce QERR_BLOCK_JOB_NOT_ACTIVE
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 02/36] qerror/block: introduce QERR_BLOCK_JOB_NOT_ACTIVE Paolo Bonzini
@ 2012-06-15 16:51   ` Eric Blake
  2012-06-15 16:56     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 16:51 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 1359 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> The DeviceNotActive error is not a particularly good match, add
> a separate one.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  blockdev.c       |    4 ++--
>  qapi-schema.json |    5 ++---
>  qerror.c         |    4 ++++
>  qerror.h         |    3 +++
>  4 files changed, 11 insertions(+), 5 deletions(-)

Looks reasonable, but it will affect libvirt:  Right now, libvirt parses
the particular return code (qemu_monitor.c:qemuMonitorJSONBlockJob), and
translates

DeviceNotActive => "No active operation on device"
...
all others => "Unexpected error"

so if this patch goes in but libvirt doesn't match, then libvirt will
have a regression in error message quality.  Not the end of the world,
though, so don't hold back just because of libvirt.

Reviewed-by: Eric Blake <eblake@redhat.com>

> @@ -1679,8 +1679,7 @@
>  # @device: the device name
>  #
>  # Returns: Nothing on success
> -#          If no background operation is active on this device, DeviceNotActive
> -#          If cancellation already in progress, DeviceInUse
> +#          If no background operation is active on this device, BlockJobNotActive

What happened to DeviceInUse?

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 02/36] qerror/block: introduce QERR_BLOCK_JOB_NOT_ACTIVE
  2012-06-15 16:51   ` Eric Blake
@ 2012-06-15 16:56     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-06-15 16:56 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

Il 15/06/2012 18:51, Eric Blake ha scritto:
> Looks reasonable, but it will affect libvirt:  Right now, libvirt parses
> the particular return code (qemu_monitor.c:qemuMonitorJSONBlockJob), and
> translates
> 
> DeviceNotActive => "No active operation on device"
> ...
> all others => "Unexpected error"
> 
> so if this patch goes in but libvirt doesn't match, then libvirt will
> have a regression in error message quality.  Not the end of the world,
> though, so don't hold back just because of libvirt.
> Reviewed-by: Eric Blake <eblake@redhat.com>
> 
>> > @@ -1679,8 +1679,7 @@
>> >  # @device: the device name
>> >  #
>> >  # Returns: Nothing on success
>> > -#          If no background operation is active on this device, DeviceNotActive
>> > -#          If cancellation already in progress, DeviceInUse
>> > +#          If no background operation is active on this device, BlockJobNotActive
> What happened to DeviceInUse?

It wasn't being sent in reality. :)

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 05/36] block: add support for job pause/resume
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 05/36] block: add support for job pause/resume Paolo Bonzini
@ 2012-06-15 17:22   ` Eric Blake
  0 siblings, 0 replies; 65+ messages in thread
From: Eric Blake @ 2012-06-15 17:22 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 1312 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> Job pausing reuses the existing support for cancellable sleeps.  A pause
> happens at the next sleeping point and lasts until the coroutine is
> re-entered explicitly.  Cancellation was already doing a forced resume,
> so implement it explicitly in terms of resume.
> 
> Paused jobs cannot be canceled without first resuming them.  This ensures
> that I/O errors are never missed by management.
> 

> +++ b/qapi-schema.json
> @@ -906,6 +906,8 @@
>  #
>  # @len: the maximum progress value
>  #
> +# @paused: whether the job is paused
> +#
>  # @offset: the current progress value
>  #
>  # @speed: the rate limit, bytes per second
> @@ -914,7 +916,7 @@
>  ##
>  { 'type': 'BlockJobInfo',
>    'data': {'type': 'str', 'device': 'str', 'len': 'int',
> -           'offset': 'int', 'speed': 'int'} }
> +           'offset': 'int', 'paused': 'bool', 'speed': 'int'} }

Should this be listed as '*paused': 'bool', as an indication that not
all versions of qemu populated the paused member?  It's an output-only
type, though, so I think we are okay in providing more information that
what clients were previously used to seeing.

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/36] qmp: add block-job-pause and block-job-resume
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 06/36] qmp: add block-job-pause and block-job-resume Paolo Bonzini
@ 2012-06-15 17:32   ` Eric Blake
  2012-07-11 16:02     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 17:32 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 2014 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> Add QMP commands matching the functionality.
> 
> Paused jobs cannot be canceled without first resuming them.  This
> ensures that I/O errors are never missed by management.  However, an
> optional force argument can be specified to allow that.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> +++ b/qapi-schema.json

> +##
> +# @block-job-pause:
> +#
> +# Pause an active background block operation.
> +#
> +# This command returns immediately after marking the active background block
> +# operation for pausing.  It is an error to call this command if no
> +# operation is in progress.
> +#
> +# The operation will pause as soon as possible.  No event is emitted when
> +# the operation is actually paused.  Cancelling a paused job automatically

US (Canceling) vs. UK (Cancelling)

> +# resumes it.
> +#
> +# @device: the device name
> +#
> +# Returns: Nothing on success
> +#          If no background operation is active on this device, DeviceNotActive
> +#
> +# Since: 1.1

1.2

> +# @block-job-resume:
> +#
> +# Resume an active background block operation.
> +#
> +# This command returns immediately after resuming a paused background block
> +# operation for cancellation.  It is an error to call this command if no
> +# operation is in progress.
> +#
> +# @device: the device name
> +#
> +# Returns: Nothing on success
> +#          If no background operation is active on this device, DeviceNotActive
> +#          If cancellation already in progress, DeviceInUse
>  #
>  # Since: 1.1

1.2

Just so I'm clear, am I right that:

calling block-job-pause multiple times means the subsequent calls are
successful no-ops; likewise, calling block-job-resume on a job not
paused (whether by calling multiple times, or if the job never paused in
the first place) is a successful no-op.

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 12/36] block: sort BlockDeviceIoStatus errors by severity
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 12/36] block: sort BlockDeviceIoStatus errors by severity Paolo Bonzini
@ 2012-06-15 17:45   ` Eric Blake
  2012-07-11 16:03     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 17:45 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 557 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:

> +++ b/qapi-schema.json
> @@ -429,7 +429,7 @@
>  #
>  # Since: 1.0
>  ##
> -{ 'enum': 'BlockDeviceIoStatus', 'data': [ 'ok', 'failed', 'nospace' ] }
> +{ 'enum': 'BlockDeviceIoStatus', 'data': [ 'ok', 'nospace', 'failed' ] }

Isn't this an ABI change?  Do we have any generated or client code where
'failed' mapping to 1 (pre-patch) vs. 2 (post-patch) would cause us
back-compat grief?

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 13/36] block: introduce block job error
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 13/36] block: introduce block job error Paolo Bonzini
@ 2012-06-15 17:50   ` Eric Blake
  2012-07-11 16:10     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 17:50 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 1815 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> The following behaviors are possible:
> 
> 'report': The behavior is the same as in 1.1.  An I/O error,
> respectively during a read or a write, will complete the job immediately
> with an error code.
> 
> 'ignore': An I/O error, respectively during a read or a write, will be
> ignored.  For streaming, the job will complete with an error and the
> backing file will be left in place.  For mirroring, the sector will be
> marked again as dirty and re-examined later.
> 
> 'stop': The job will be paused and the job iostatus will be set to
> failed or nospace, while the VM will keep running.  This can only be
> specified if the block device has rerror=stop and werror=stop or enospc.
> 
> 'enospc': Behaves as 'stop' for ENOSPC errors, 'report' for others.
> 
> In all cases, even for 'report', the I/O error is reported as a QMP
> event BLOCK_JOB_ERROR, with the same arguments as BLOCK_IO_ERROR.

Looks reasonable.

> +++ b/qapi-schema.json
> @@ -935,11 +935,14 @@
>  #
>  # @speed: the rate limit, bytes per second
>  #
> +# @io-status: the status of the job
> +#
>  # Since: 1.1
>  ##
>  { 'type': 'BlockJobInfo',
>    'data': {'type': 'str', 'device': 'str', 'len': 'int',
> -           'offset': 'int', 'paused': 'bool', 'speed': 'int'} }
> +           'offset': 'int', 'paused': 'bool', 'speed': 'int',
> +           'io-status': 'BlockDeviceIoStatus'} }

Again, when adding members to output-only types compared to a previous
release, should we be marking the additions as optional, as in:

'*io-status': 'BlockDeviceIoStatus'

to indicate that the member may not be present when talking to older qemu?

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 14/36] stream: add on_error argument
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 14/36] stream: add on_error argument Paolo Bonzini
@ 2012-06-15 17:58   ` Eric Blake
  2012-07-11 16:12     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 17:58 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 1396 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> This patch adds support for error management to streaming.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  block/stream.c   |   28 +++++++++++++++++++++++++++-
>  block_int.h      |    3 ++-
>  blockdev.c       |   11 ++++++++---
>  hmp.c            |    3 ++-
>  qapi-schema.json |    9 ++++++---
>  qmp-commands.hx  |    2 +-
>  6 files changed, 46 insertions(+), 10 deletions(-)
> 

> +++ b/qapi-schema.json
> @@ -1650,6 +1650,8 @@
>  #
>  # @speed:  #optional the maximum speed, in bytes per second
>  #
> +# @on_error: #optional the action to take on an error (default report)
> +#
>  # Returns: Nothing on success
>  #          If streaming is already active on this device, DeviceInUse
>  #          If @device does not exist, DeviceNotFound
> @@ -1657,10 +1659,11 @@
>  #          If @base does not exist, BaseNotFound
>  #          If @speed is invalid, InvalidParameter

Should you mention the possibility of the new
QERR_INVALID_PARAMETER_COMBINATION error when on_error requests an
action that the device doesn't support?

>  #
> -# Since: 1.1
> +# Since: 1.1, on_error since 1.2

First instance of a double since.  I like the idea; should we be using
it in more places?

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 22/36] block: add mirror job
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 22/36] block: add mirror job Paolo Bonzini
@ 2012-06-15 18:20   ` Eric Blake
  2012-07-12 13:45     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 18:20 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 2764 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> This patch adds the implementation of a new job that mirrors a disk to
> a new image while letting the guest continue using the old image.
> The target is treated as a "black box" and data is copied from the
> source to the target in the background.
> 
> The mirror job is never-ending, but it is logically structured into
> two phases: 1) copy all data as fast as possible until the target
> first gets in sync with the source; 2) keep target in sync and
> ensure that reopening to the target gets a correct (full) copy
> of the source data.
> 
> The second phase is indicated by the progress in "info block-jobs"
> reporting the current offset to be equal to the length of the file.
> When the job is cancelled in the second phase, QEMU will run the
> job until the source is clean and quiescent, then it will report
> successful completion of the job.  (Note that it could already happen
> that management lost the race against QEMU and got a completion
> event instead of cancellation).

Is this paragraph still accurate?  At any rate, it sounds a bit
confusing - if I run 'block-job-cancel', do I get a 'complete' or a
'cancelled' event?  Or is it a question of _which_ event I get depending
on how a race goes?


> +
> +        if (bdrv_get_dirty_count(bs) == 0) {
> +            /* We're out of the streaming phase.  From now on, if the
> +             * job is cancelled we will actually complete all pending
> +             * I/O and report completion, so that drive-reopen can be
> +             * used to pivot to the mirroring target.
> +             */

Comment is out of date since you are no longer proposing a drive-reopen,
but block-job-complete.

>  ##
> +# @MirrorSyncMode:
> +#
> +# An enumeration of possible behaviors for the initial synchronization
> +# phase of storage mirroring.
> +#
> +# @top: copies data in the topmost image to the destination
> +#
> +# @full: copies data from all images to the destination

Do we ever want to support partial mirroring, the way we support partial
block-stream?  That is, given:

A <- B <- C

and creating a copy D, 'top' means D has B as a backing file, 'full'
means D has no backing file, but to create D with A as a backing file, I
would instead have to do a 'top' mirror, then complete, then do a
regular block-stream for a partial pull.

Deciding whether to support partial block mirror with anything more than
top but less than full will impact whether my proposed libvirt
virDomainBlockCopy() command needs an extra argument for naming the new
base file in a partial mirror.

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 23/36] qmp: add drive-mirror command
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 23/36] qmp: add drive-mirror command Paolo Bonzini
@ 2012-06-15 20:12   ` Eric Blake
  2012-07-11 16:23     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 20:12 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 2950 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> This adds the monitor commands that start the mirroring job.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  blockdev.c       |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
>  hmp-commands.hx  |   21 ++++++++++
>  hmp.c            |   28 +++++++++++++
>  hmp.h            |    1 +
>  qapi-schema.json |   33 +++++++++++++++
>  qmp-commands.hx  |   41 +++++++++++++++++++
>  trace-events     |    2 +-
>  7 files changed, 242 insertions(+), 4 deletions(-)
> 

>  ##
> +# @drive-mirror
> +#
> +# Start mirroring a block device's writes to a new destination.
> +#
> +# @device:  the name of the device whose writes should be mirrored.
> +#
> +# @target: the target of the new image. If the file exists, or if it
> +#          is a device, the existing file/device will be used as the new
> +#          destination.  If it does not exist, a new file will be created.
> +#
> +# @format: #optional the format of the new destination, default is the
> +#          format of the source
> +#
> +# @mode: #optional whether and how QEMU should create a new image, default is
> +# 'absolute-paths'.

Indentation.

> +#
> +# @sync: what parts of the disk image should be copied to the destination
> +#        (all the disk, only the sectors allocated in the topmost image, or
> +#        only new I/O).

Document @peed:.

> +#
> +# Returns: nothing on success
> +#          If @device is not a valid block device, DeviceNotFound
> +#          If @target can't be opened, OpenFileFailed
> +#          If @format is invalid, InvalidBlockFormat
> +#
> +# Since 1.1

1.2

> +##
> +{ 'command': 'drive-mirror',
> +  'data': { 'device': 'str', 'target': 'str', '*format': 'str',
> +            'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
> +            '*speed': 'int' } }
> +
> +##
>  # @migrate_cancel
>  #
>  # Cancel the current executing migration process.
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index e2d77b6..cccea2f 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -832,6 +832,47 @@ Example:
>  EQMP
>  
>      {
> +        .name       = "drive-mirror",
> +        .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?",

sync: twice? and no speed: or mode:?


> +SQMP
> +drive-mirror
> +------------
> +
> +Start mirroring a block device's writes to a new destination. target
> +specifies the target of the new image. If the file exists, or if it is
> +a device, it will be used as the new destination for writes. If does not
> +exist, a new file will be created. format specifies the format of the
> +mirror image, default is to probe if mode='existing', else qcow2.

default is qcow2?  Earlier in this patch you said the same format as the
source.

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 25/36] mirror: add support for on_source_error/on_target_error
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 25/36] mirror: add support for on_source_error/on_target_error Paolo Bonzini
@ 2012-06-15 21:12   ` Eric Blake
  2012-07-11 16:28     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 21:12 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 1555 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> (Mostly untested).
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  block/mirror.c   |   50 +++++++++++++++++++++++++++++++++++++++++---------
>  block_int.h      |    4 ++++
>  blockdev.c       |   14 ++++++++++++--
>  hmp.c            |    3 ++-
>  qapi-schema.json |    7 ++++++-
>  qmp-commands.hx  |    8 +++++++-
>  6 files changed, 72 insertions(+), 14 deletions(-)

> +++ b/qapi-schema.json
> @@ -1379,6 +1379,10 @@
>  #        (all the disk, only the sectors allocated in the topmost image, or
>  #        only new I/O).
>  #
> +# @on_source_error: #optional the action to take on an error on the source
> +#
> +# @on_target_error: #optional the action to take on an error on the target

Mention the default when not provided.

> +#
>  # Returns: nothing on success
>  #          If @device is not a valid block device, DeviceNotFound
>  #          If @target can't be opened, OpenFileFailed
> @@ -1389,7 +1393,8 @@
>  { 'command': 'drive-mirror',
>    'data': { 'device': 'str', 'target': 'str', '*format': 'str',
>              'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
> -            '*speed': 'int' } }
> +            '*speed': 'int', '*on_source_error': 'BlockdevOnError',
> +            '*on_target_error': 'BlockdevOnError' } }

QMP should prefer - over _; shouldn't this be '*on-source-error' and
'*on-target-error'?

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 26/36] block: live snapshot documentation tweaks
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 26/36] block: live snapshot documentation tweaks Paolo Bonzini
@ 2012-06-15 21:14   ` Eric Blake
  2012-07-11 16:16     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 21:14 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 1347 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  qapi-schema.json |    4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 2ee988b..250edf2 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -1289,6 +1289,7 @@
>  #
>  # Returns: nothing on success
>  #          If @device is not a valid block device, DeviceNotFound
> +#          If the block device has no medium inserted, DeviceHasNoMedium
>  #          If @device is busy, DeviceInUse will be returned
>  #          If @snapshot-file can't be created, OpenFileFailed
>  #          If @snapshot-file can't be opened, OpenFileFailed

Can we combine these?

If @snapshot-file can't be opened or created, OpenFileFailed

> @@ -1321,6 +1322,9 @@
>  #
>  # Returns: nothing on success
>  #          If @device is not a valid block device, DeviceNotFound
> +#          If the block device has no medium inserted, DeviceHasNoMedium
> +#          If @device is busy, DeviceInUse will be returned
> +#          If @snapshot-file can't be created, OpenFileFailed
>  #          If @snapshot-file can't be opened, OpenFileFailed

and again.

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 28/36] block: add block-job-complete
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 28/36] block: add block-job-complete Paolo Bonzini
@ 2012-06-15 21:42   ` Eric Blake
  0 siblings, 0 replies; 65+ messages in thread
From: Eric Blake @ 2012-06-15 21:42 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 2215 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> While streaming can be dropped as soon as it progressed through the whole
> image, mirroring needs to be completed manually for two reasons: 1) so that
> management knows exactly when the VM switches to the target; 2) because
> for other use cases such as replication, we may leave the operation running
> for the whole life of the virtual machine.
> 
> Add a new block job command that manually completes background operations.
> 


>  ##
> +# @block-job-complete:
> +#
> +# Manually trigger completion of an active background block operation.  This
> +# is supported for drive mirroring, where it also switches the device to
> +# write to the target path only.
> +#
> +# This command completes an active background block operation synchronously.
> +# The ordering of this command's return with the BLOCK_JOB_COMPLETED event
> +# is not defined.  Note that if an I/O error occurs during the processing of
> +# this command: 1) the command itself will fail; 2) the error will be processed
> +# according to the rerror/werror arguments that were specified when starting
> +# the operation.
> +#
> +# A cancelled or paused job cannot be completed.

US vs. UK spelling.

So 'block-job-complete' ends by pivoting or errors because it is not
ready, 'block-job-cancel' without force ends with a clean mirror without
pivoting or errors because it is not ready, and 'block-job-cancel' with
force ends no matter what, even if it means a non-clean mirror.  I think
that was all the cases I asked for regarding a single disk.  And
hopefully we still have time to add additional patches to also support
'transaction' of combinations of these block actions across multiple disks.

> +#
> +# @device: the device name
> +#
> +# Returns: Nothing on success
> +#          If no background operation is active on this device, BlockJobNotActive
> +#          If the operation cannot be completed manually (either in general, or
> +#            not at the time the command is invoked), BlockJobNotReady
> +#
> +# Since: 1.1

1.2

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 30/36] add hierarchical bitmap data type and test cases
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 30/36] add hierarchical bitmap data type and test cases Paolo Bonzini
@ 2012-06-15 23:02   ` Eric Blake
  2012-07-11 16:35     ` Paolo Bonzini
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Blake @ 2012-06-15 23:02 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 3262 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> HBitmaps provides an array of bits.  The bits are stored as usual in an
> array of unsigned longs, but HBitmap is also optimized to provide fast
> iteration over set bits; going from one bit to the next is O(log log n)
> worst case, which is low enough that the number of levels is in fact fixed.
> 

> 
> Setting or clearing a range of m bits on all levels, the work to perform
> is O(m + m/W + m/W^2 + ...), which in O(m) like on a regular bitmap.

s/in/is/ (in commit message and code comment)

I made the mistake of starting on the .c before the .h, and my first
comments were:

> +struct HBitmap {
> +    uint64_t size;

Number of total bits in the bottom level?

> +    uint64_t count;

Number of set bits in the bottom level?

> +    int granularity;

A scaling factor, so that you can iterate over addresses of a sector
(512 bytes at a time) instead of having to scale the bit result?  [Hint
- these names are not self-obvious, so a short comment might help the
reader]

> +    unsigned long *levels[HBITMAP_LEVELS];

and at this point, I decided reading the .h first makes more sense.
Also, this is a high-level first-impressions review, not a line-by-line
algorithmic accuracy review.  Did you invent this yourself, or copy from
the ideas from a published work?

> +};
> +
> +static int64_t hbi_next_internal(HBitmapIter *hbi)
> +{

> +
> +        /* Check for end of iteration.  We only use up to
> +         * BITS_PER_LEVEL bits (actually less) in the level 0 bitmap,
> +         * and a sentinel is placed in hbitmap_alloc that ends the
> +         * above loop.

Level 0 being the coursest (top, each bit represents that at least one
page of level 1 has a set bit), and level n being the finest (each bit
representing the actual bitmap?

> +         */
> +
> +        if (i == 0 && (cur & (BITS_PER_LONG - 1)) == 0) {
> +            return -1;
> +        }
> +        for (; i < HBITMAP_LEVELS - 1; i++) {
> +            /* Find least significant set bit in the word, use them
> +             * to add back shifted out bits to pos.
> +             */
> +            pos = (pos << BITS_PER_LEVEL) + ffsl(cur) - 1;

ffsl() is a glibc extension.  Do we have a fallback for other platforms?
 (Only ffs() is POSIX).

> +
> +/* The recursive workhorse... */
> +static void hb_set_between(HBitmap *hb, int level, uint64_t start, uint64_t end)

And the recursion is bounded by HBITMAP_LEVELS, which is relatively
small, right?


> +
> +HBitmap *hbitmap_alloc(uint64_t size, int granularity)
> +{
> +    HBitmap *hb = g_malloc0(sizeof (struct HBitmap));
> +    int i;
> +
> +    size = (size + (1 << granularity) - 1) >> granularity;

Doesn't this mean that granularity > 31 or < 0 gives undefined behavior?
 Should you be validating it for being in range?


> +
> +/* For 32-bit, the largest that fits in a 4 GiB address space.
> + * For 64-bit, the number of sectors in 1 PiB.  Good luck, in
> + * either case... :)
> + */
> +#define HBITMAP_LOG_MAX_SIZE   (BITS_PER_LONG == 32 ? 34 : 41)

Indeed :)

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 36/36] mirror: allow customizing the granularity
  2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 36/36] mirror: allow customizing the granularity Paolo Bonzini
@ 2012-06-15 23:24   ` Eric Blake
  0 siblings, 0 replies; 65+ messages in thread
From: Eric Blake @ 2012-06-15 23:24 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

[-- Attachment #1: Type: text/plain, Size: 772 bytes --]

On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> The desired granularity may be very different depending on the kind of operation
> (e.g. continous replication vs. collapse-to-raw) and whether the VM is expected
> to perform lots of I/O while mirroring is in progress.  Allow the user to customize
> it.

> +++ b/qapi-schema.json
> @@ -1383,6 +1383,8 @@
>  #        (all the disk, only the sectors allocated in the topmost image, or
>  #        only new I/O).
>  #
> +# @granularity: #optional granularity of the dirty bitmap, default is 64K.

What happens if the user doesn't pass in a power of 2?  Is there a
minimum or maximum acceptable value?

-- 
Eric Blake   eblake@redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 620 bytes --]

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands
  2012-06-15 16:45   ` Eric Blake
@ 2012-07-11 16:00     ` Paolo Bonzini
  2012-07-12  8:07       ` Kevin Wolf
  0 siblings, 1 reply; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:00 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, stefanha, lcapitulino

Il 15/06/2012 18:45, Eric Blake ha scritto:
> Pre-existing, but do we have any policy on US (canceled) vs. UK
> (cancelled) spelling?  Obviously, our API spelling is locked into UK
> spelling, but if the docs generally prefer US spelling, do we need to
> adjust things here?  But any such adjustments should be separate
> commits, so no impact to my reviewed-by.

I had no idea that it was UK vs. US spelling.  I just assumed canceled
was wrong. :)  I would use UK spelling consistently for this word.

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/36] qmp: add block-job-pause and block-job-resume
  2012-06-15 17:32   ` Eric Blake
@ 2012-07-11 16:02     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:02 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, stefanha, lcapitulino

Il 15/06/2012 19:32, Eric Blake ha scritto:
> Just so I'm clear, am I right that:
> 
> calling block-job-pause multiple times means the subsequent calls are
> successful no-ops; likewise, calling block-job-resume on a job not
> paused (whether by calling multiple times, or if the job never paused in
> the first place) is a successful no-op.

Yes, the calls do not cumulate.

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 12/36] block: sort BlockDeviceIoStatus errors by severity
  2012-06-15 17:45   ` Eric Blake
@ 2012-07-11 16:03     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:03 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, stefanha, lcapitulino

Il 15/06/2012 19:45, Eric Blake ha scritto:
>> > +++ b/qapi-schema.json
>> > @@ -429,7 +429,7 @@
>> >  #
>> >  # Since: 1.0
>> >  ##
>> > -{ 'enum': 'BlockDeviceIoStatus', 'data': [ 'ok', 'failed', 'nospace' ] }
>> > +{ 'enum': 'BlockDeviceIoStatus', 'data': [ 'ok', 'nospace', 'failed' ] }
> Isn't this an ABI change?  Do we have any generated or client code where
> 'failed' mapping to 1 (pre-patch) vs. 2 (post-patch) would cause us
> back-compat grief?

No, we do not generate any code (yet?) for consumption outside QEMU
itself.  In particular we do not install any generated header nor do we
use the generated enums in a (static or shared) library.

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 13/36] block: introduce block job error
  2012-06-15 17:50   ` Eric Blake
@ 2012-07-11 16:10     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:10 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, stefanha, lcapitulino

Il 15/06/2012 19:50, Eric Blake ha scritto:
>> >  { 'type': 'BlockJobInfo',
>> >    'data': {'type': 'str', 'device': 'str', 'len': 'int',
>> > -           'offset': 'int', 'paused': 'bool', 'speed': 'int'} }
>> > +           'offset': 'int', 'paused': 'bool', 'speed': 'int',
>> > +           'io-status': 'BlockDeviceIoStatus'} }
> Again, when adding members to output-only types compared to a previous
> release, should we be marking the additions as optional, as in:
> 
> '*io-status': 'BlockDeviceIoStatus'
> 
> to indicate that the member may not be present when talking to older qemu?

I don't think so, but still we should add it to the documentation.

If in the future we will generate code that exports the types, these
should not be indicated as optional IMHO.  Optional is more like a JSON
null value (in fact in Javascript a['nonexistent'] is null).  Instead,
we should add a new notation indicating that this could have a default
value (false in the case of 'paused', 'ok' here), and the generated code
would synthesize the default value if the JSON lacks the item.

This could be used also in some commands.  We mark some arguments as
optional, but it would be easier to present them as having a default
value.  In particular, in a C API likely you would prefer to map such
cases to one C arguments rather than a pair "has_foo"+"foo".

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 14/36] stream: add on_error argument
  2012-06-15 17:58   ` Eric Blake
@ 2012-07-11 16:12     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:12 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, stefanha, lcapitulino

Il 15/06/2012 19:58, Eric Blake ha scritto:
> On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
>> This patch adds support for error management to streaming.
>>
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> ---
>>  block/stream.c   |   28 +++++++++++++++++++++++++++-
>>  block_int.h      |    3 ++-
>>  blockdev.c       |   11 ++++++++---
>>  hmp.c            |    3 ++-
>>  qapi-schema.json |    9 ++++++---
>>  qmp-commands.hx  |    2 +-
>>  6 files changed, 46 insertions(+), 10 deletions(-)
>>
> 
>> +++ b/qapi-schema.json
>> @@ -1650,6 +1650,8 @@
>>  #
>>  # @speed:  #optional the maximum speed, in bytes per second
>>  #
>> +# @on_error: #optional the action to take on an error (default report)
>> +#
>>  # Returns: Nothing on success
>>  #          If streaming is already active on this device, DeviceInUse
>>  #          If @device does not exist, DeviceNotFound
>> @@ -1657,10 +1659,11 @@
>>  #          If @base does not exist, BaseNotFound
>>  #          If @speed is invalid, InvalidParameter
> 
> Should you mention the possibility of the new
> QERR_INVALID_PARAMETER_COMBINATION error when on_error requests an
> action that the device doesn't support?

Yes, but I'll just change it to InvalidParameter.

>>  #
>> -# Since: 1.1
>> +# Since: 1.1, on_error since 1.2
> 
> First instance of a double since.  I like the idea; should we be using
> it in more places?

Yes, but I'll move the "since" to the description of on_error.

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 26/36] block: live snapshot documentation tweaks
  2012-06-15 21:14   ` Eric Blake
@ 2012-07-11 16:16     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:16 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, stefanha, lcapitulino

Il 15/06/2012 23:14, Eric Blake ha scritto:
> On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> ---
>>  qapi-schema.json |    4 ++++
>>  1 file changed, 4 insertions(+)
>>
>> diff --git a/qapi-schema.json b/qapi-schema.json
>> index 2ee988b..250edf2 100644
>> --- a/qapi-schema.json
>> +++ b/qapi-schema.json
>> @@ -1289,6 +1289,7 @@
>>  #
>>  # Returns: nothing on success
>>  #          If @device is not a valid block device, DeviceNotFound
>> +#          If the block device has no medium inserted, DeviceHasNoMedium
>>  #          If @device is busy, DeviceInUse will be returned
>>  #          If @snapshot-file can't be created, OpenFileFailed
>>  #          If @snapshot-file can't be opened, OpenFileFailed
> 
> Can we combine these?
> 
> If @snapshot-file can't be opened or created, OpenFileFailed
> 
>> @@ -1321,6 +1322,9 @@
>>  #
>>  # Returns: nothing on success
>>  #          If @device is not a valid block device, DeviceNotFound
>> +#          If the block device has no medium inserted, DeviceHasNoMedium
>> +#          If @device is busy, DeviceInUse will be returned
>> +#          If @snapshot-file can't be created, OpenFileFailed
>>  #          If @snapshot-file can't be opened, OpenFileFailed
> 
> and again.
> 

Yes, done.

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 23/36] qmp: add drive-mirror command
  2012-06-15 20:12   ` Eric Blake
@ 2012-07-11 16:23     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:23 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

Il 15/06/2012 22:12, Eric Blake ha scritto:
> On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
>> > This adds the monitor commands that start the mirroring job.
>> > 
>> > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> > ---
>> >  blockdev.c       |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
>> >  hmp-commands.hx  |   21 ++++++++++
>> >  hmp.c            |   28 +++++++++++++
>> >  hmp.h            |    1 +
>> >  qapi-schema.json |   33 +++++++++++++++
>> >  qmp-commands.hx  |   41 +++++++++++++++++++
>> >  trace-events     |    2 +-
>> >  7 files changed, 242 insertions(+), 4 deletions(-)
>> > 
>> >  ##
>> > +# @drive-mirror
>> > +#
>> > +# Start mirroring a block device's writes to a new destination.
>> > +#
>> > +# @device:  the name of the device whose writes should be mirrored.
>> > +#
>> > +# @target: the target of the new image. If the file exists, or if it
>> > +#          is a device, the existing file/device will be used as the new
>> > +#          destination.  If it does not exist, a new file will be created.
>> > +#
>> > +# @format: #optional the format of the new destination, default is the
>> > +#          format of the source
>> > +#
>> > +# @mode: #optional whether and how QEMU should create a new image, default is
>> > +# 'absolute-paths'.
> Indentation.
> 
>> > +#
>> > +# @sync: what parts of the disk image should be copied to the destination
>> > +#        (all the disk, only the sectors allocated in the topmost image, or
>> > +#        only new I/O).
> Document @peed:.
> 
>> > +#
>> > +# Returns: nothing on success
>> > +#          If @device is not a valid block device, DeviceNotFound
>> > +#          If @target can't be opened, OpenFileFailed
>> > +#          If @format is invalid, InvalidBlockFormat
>> > +#
>> > +# Since 1.1
> 1.2
> 
>> > +##
>> > +{ 'command': 'drive-mirror',
>> > +  'data': { 'device': 'str', 'target': 'str', '*format': 'str',
>> > +            'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
>> > +            '*speed': 'int' } }
>> > +
>> > +##
>> >  # @migrate_cancel
>> >  #
>> >  # Cancel the current executing migration process.
>> > diff --git a/qmp-commands.hx b/qmp-commands.hx
>> > index e2d77b6..cccea2f 100644
>> > --- a/qmp-commands.hx
>> > +++ b/qmp-commands.hx
>> > @@ -832,6 +832,47 @@ Example:
>> >  EQMP
>> >  
>> >      {
>> > +        .name       = "drive-mirror",
>> > +        .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?",
> sync: twice? and no speed: or mode:?
> 
> 
>> > +SQMP
>> > +drive-mirror
>> > +------------
>> > +
>> > +Start mirroring a block device's writes to a new destination. target
>> > +specifies the target of the new image. If the file exists, or if it is
>> > +a device, it will be used as the new destination for writes. If does not
>> > +exist, a new file will be created. format specifies the format of the
>> > +mirror image, default is to probe if mode='existing', else qcow2.
> default is qcow2?  Earlier in this patch you said the same format as the
> source.

Indeed, thanks for pointing out all the inconsistencies.

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 25/36] mirror: add support for on_source_error/on_target_error
  2012-06-15 21:12   ` Eric Blake
@ 2012-07-11 16:28     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:28 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

Il 15/06/2012 23:12, Eric Blake ha scritto:
>> > +#
>> >  # Returns: nothing on success
>> >  #          If @device is not a valid block device, DeviceNotFound
>> >  #          If @target can't be opened, OpenFileFailed
>> > @@ -1389,7 +1393,8 @@
>> >  { 'command': 'drive-mirror',
>> >    'data': { 'device': 'str', 'target': 'str', '*format': 'str',
>> >              'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
>> > -            '*speed': 'int' } }
>> > +            '*speed': 'int', '*on_source_error': 'BlockdevOnError',
>> > +            '*on_target_error': 'BlockdevOnError' } }
> QMP should prefer - over _; shouldn't this be '*on-source-error' and
> '*on-target-error'?

Will fix.

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 30/36] add hierarchical bitmap data type and test cases
  2012-06-15 23:02   ` Eric Blake
@ 2012-07-11 16:35     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-11 16:35 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, lcapitulino, qemu-devel, stefanha

Il 16/06/2012 01:02, Eric Blake ha scritto:
>> > +    unsigned long *levels[HBITMAP_LEVELS];
> and at this point, I decided reading the .h first makes more sense.
> Also, this is a high-level first-impressions review, not a line-by-line
> algorithmic accuracy review.  Did you invent this yourself, or copy from
> the ideas from a published work?

I came up with this, and actually the comment is wrong because the
complexity is log32 N or log64 N, not log log N.  I first tried to go
for doubly logarithmic, but it didn't make much sense.

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands
  2012-07-11 16:00     ` Paolo Bonzini
@ 2012-07-12  8:07       ` Kevin Wolf
  2012-07-12 20:41         ` Blue Swirl
  0 siblings, 1 reply; 65+ messages in thread
From: Kevin Wolf @ 2012-07-12  8:07 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: Eric Blake, qemu-devel, stefanha, lcapitulino

Am 11.07.2012 18:00, schrieb Paolo Bonzini:
> Il 15/06/2012 18:45, Eric Blake ha scritto:
>> Pre-existing, but do we have any policy on US (canceled) vs. UK
>> (cancelled) spelling?  Obviously, our API spelling is locked into UK
>> spelling, but if the docs generally prefer US spelling, do we need to
>> adjust things here?  But any such adjustments should be separate
>> commits, so no impact to my reviewed-by.
> 
> I had no idea that it was UK vs. US spelling.  I just assumed canceled
> was wrong. :)  I would use UK spelling consistently for this word.

Both UK and US spellings are widely used within qemu and the advantages
of a standardisation on one of them are probably not worth discussions
about which one is "better". Whoever writes the code, decides what
spelling he uses.

And yes, "canceled" looks ugly to me. ;-)

Kevin

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 22/36] block: add mirror job
  2012-06-15 18:20   ` Eric Blake
@ 2012-07-12 13:45     ` Paolo Bonzini
  0 siblings, 0 replies; 65+ messages in thread
From: Paolo Bonzini @ 2012-07-12 13:45 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, stefanha, lcapitulino

Il 15/06/2012 20:20, Eric Blake ha scritto:
>> > The second phase is indicated by the progress in "info block-jobs"
>> > reporting the current offset to be equal to the length of the file.
>> > When the job is cancelled in the second phase, QEMU will run the
>> > job until the source is clean and quiescent, then it will report
>> > successful completion of the job.  (Note that it could already happen
>> > that management lost the race against QEMU and got a completion
>> > event instead of cancellation).
> Is this paragraph still accurate?  At any rate, it sounds a bit
> confusing - if I run 'block-job-cancel', do I get a 'complete' or a
> 'cancelled' event?  Or is it a question of _which_ event I get depending
> on how a race goes?

For streaming, the latter.  Same for mirroring in the first phase.

Mirroring in the second phase will always give you an error or completed
event.

Paolo

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands
  2012-07-12  8:07       ` Kevin Wolf
@ 2012-07-12 20:41         ` Blue Swirl
  2012-07-13  9:13           ` Kevin Wolf
  0 siblings, 1 reply; 65+ messages in thread
From: Blue Swirl @ 2012-07-12 20:41 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: Paolo Bonzini, lcapitulino, Eric Blake, qemu-devel, stefanha

On Thu, Jul 12, 2012 at 8:07 AM, Kevin Wolf <kwolf@redhat.com> wrote:
> Am 11.07.2012 18:00, schrieb Paolo Bonzini:
>> Il 15/06/2012 18:45, Eric Blake ha scritto:
>>> Pre-existing, but do we have any policy on US (canceled) vs. UK
>>> (cancelled) spelling?  Obviously, our API spelling is locked into UK
>>> spelling, but if the docs generally prefer US spelling, do we need to
>>> adjust things here?  But any such adjustments should be separate
>>> commits, so no impact to my reviewed-by.
>>
>> I had no idea that it was UK vs. US spelling.  I just assumed canceled
>> was wrong. :)  I would use UK spelling consistently for this word.
>
> Both UK and US spellings are widely used within qemu and the advantages
> of a standardisation on one of them are probably not worth discussions
> about which one is "better". Whoever writes the code, decides what
> spelling he uses.

The code should be written in clear English, but let's standardize the
variant only after there are no other spelling or grammar errors :).

But the user visible interface should use only either UK or US
spelling consistently, the other one should be handled by UI
localization. Though there probably is no interest to l10n, I can't
remember anyone ever asking.

>
> And yes, "canceled" looks ugly to me. ;-)
>
> Kevin
>

^ permalink raw reply	[flat|nested] 65+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands
  2012-07-12 20:41         ` Blue Swirl
@ 2012-07-13  9:13           ` Kevin Wolf
  0 siblings, 0 replies; 65+ messages in thread
From: Kevin Wolf @ 2012-07-13  9:13 UTC (permalink / raw)
  To: Blue Swirl; +Cc: Paolo Bonzini, lcapitulino, Eric Blake, qemu-devel, stefanha

Am 12.07.2012 22:41, schrieb Blue Swirl:
> On Thu, Jul 12, 2012 at 8:07 AM, Kevin Wolf <kwolf@redhat.com> wrote:
>> Am 11.07.2012 18:00, schrieb Paolo Bonzini:
>>> Il 15/06/2012 18:45, Eric Blake ha scritto:
>>>> Pre-existing, but do we have any policy on US (canceled) vs. UK
>>>> (cancelled) spelling?  Obviously, our API spelling is locked into UK
>>>> spelling, but if the docs generally prefer US spelling, do we need to
>>>> adjust things here?  But any such adjustments should be separate
>>>> commits, so no impact to my reviewed-by.
>>>
>>> I had no idea that it was UK vs. US spelling.  I just assumed canceled
>>> was wrong. :)  I would use UK spelling consistently for this word.
>>
>> Both UK and US spellings are widely used within qemu and the advantages
>> of a standardisation on one of them are probably not worth discussions
>> about which one is "better". Whoever writes the code, decides what
>> spelling he uses.
> 
> The code should be written in clear English, but let's standardize the
> variant only after there are no other spelling or grammar errors :).
> 
> But the user visible interface should use only either UK or US
> spelling consistently, the other one should be handled by UI
> localization. Though there probably is no interest to l10n, I can't
> remember anyone ever asking.

Anthony's GTK frontend patches had some localisation (I asked because
otherwise I got mixed German/English menu entries - the standard ones
were translated, the qemu specific ones were English). Not sure what
happened with them, probably not an interesting toy any more.

We may need more localisation if we want the management tools to display
the QMP error descriptions directly to the user.

Kevin

^ permalink raw reply	[flat|nested] 65+ messages in thread

end of thread, other threads:[~2012-07-13  9:14 UTC | newest]

Thread overview: 65+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-06-15 15:05 [Qemu-devel] [RFC PATCH 00/36] A peek at the current block job patches Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 01/36] qapi: generalize documentation of streaming commands Paolo Bonzini
2012-06-15 16:45   ` Eric Blake
2012-07-11 16:00     ` Paolo Bonzini
2012-07-12  8:07       ` Kevin Wolf
2012-07-12 20:41         ` Blue Swirl
2012-07-13  9:13           ` Kevin Wolf
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 02/36] qerror/block: introduce QERR_BLOCK_JOB_NOT_ACTIVE Paolo Bonzini
2012-06-15 16:51   ` Eric Blake
2012-06-15 16:56     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 03/36] block: move job APIs to separate files Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 04/36] block: add block_job_query Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 05/36] block: add support for job pause/resume Paolo Bonzini
2012-06-15 17:22   ` Eric Blake
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 06/36] qmp: add block-job-pause and block-job-resume Paolo Bonzini
2012-06-15 17:32   ` Eric Blake
2012-07-11 16:02     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 07/36] qemu-iotests: add test for pausing a streaming operation Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 08/36] block: rename block_job_complete to block_job_completed Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 09/36] block: rename BlockErrorAction, BlockQMPEventAction Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 10/36] block: move BlockdevOnError declaration to QAPI Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 11/36] block: reorganize io error code Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 12/36] block: sort BlockDeviceIoStatus errors by severity Paolo Bonzini
2012-06-15 17:45   ` Eric Blake
2012-07-11 16:03     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 13/36] block: introduce block job error Paolo Bonzini
2012-06-15 17:50   ` Eric Blake
2012-07-11 16:10     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 14/36] stream: add on_error argument Paolo Bonzini
2012-06-15 17:58   ` Eric Blake
2012-07-11 16:12     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 15/36] qemu-iotests: add tests for streaming error handling Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 16/36] block: add bdrv_query_info Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 17/36] block: add bdrv_query_stats Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 18/36] block: make device optional in BlockInfo Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 19/36] block: add target info to QMP query-blockjobs command Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 20/36] block: forward bdrv_iostatus_reset to block job Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 21/36] block: introduce new dirty bitmap functionality Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 22/36] block: add mirror job Paolo Bonzini
2012-06-15 18:20   ` Eric Blake
2012-07-12 13:45     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 23/36] qmp: add drive-mirror command Paolo Bonzini
2012-06-15 20:12   ` Eric Blake
2012-07-11 16:23     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 24/36] mirror: support querying target file Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 25/36] mirror: add support for on_source_error/on_target_error Paolo Bonzini
2012-06-15 21:12   ` Eric Blake
2012-07-11 16:28     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 26/36] block: live snapshot documentation tweaks Paolo Bonzini
2012-06-15 21:14   ` Eric Blake
2012-07-11 16:16     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 27/36] block: add bdrv_ensure_backing_file Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 28/36] block: add block-job-complete Paolo Bonzini
2012-06-15 21:42   ` Eric Blake
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 29/36] mirror: implement completion Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 30/36] add hierarchical bitmap data type and test cases Paolo Bonzini
2012-06-15 23:02   ` Eric Blake
2012-07-11 16:35     ` Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 31/36] block: implement dirty bitmap using HBitmap Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 32/36] block: make round_to_clusters public Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 33/36] mirror: perform COW if the cluster size is bigger than the granularity Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 34/36] block: return count of dirty sectors, not chunks Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 35/36] block: allow customizing the granularity of the dirty bitmap Paolo Bonzini
2012-06-15 15:05 ` [Qemu-devel] [RFC PATCH 36/36] mirror: allow customizing the granularity Paolo Bonzini
2012-06-15 23:24   ` Eric Blake

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.