Block layer patches for 2.11.0-rc3
-----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJaHtFZAAoJEH8JsnLIjy/WYRoP/RK99nDFweOwaoBfUCyT3uc3 mHFtc5Jvo4gxkPSn0wDg+KuJ5z+BRk4S+CgyF+9Wny3ZJMiRAAt/BfNMofj4hfdD BPF5jLkvoClAFrqZe6keBadRfYjQPuhtLvrBMDoG294VsyvKoYMVrEOMV5PaWY4r RaHZY6OiADmNeMj3gCTRAae9KEGWlZ64FeYyizp1kWMwjH6I3jRZguGgA+409K0e pwJQLOUT6IaSibOuFn88IeayDy8h+XheH+FxlV0rVRRZ3rulxlpWPtUV2EE6L7rx iQk2eC+r4393sH6Owj0oVTlSCPCx4k7nZeIALcnHh6Muh/dV9mSMzItXP9ZV8YlP sAJByXoHdH3fcfSnHA7yP27pYmt0EuaVlIT+I9d4lh0bB/xRMzd5GDsx63xkzHuI kVTIQj04bYt2E3sdqfNbu/eC4imakmNIjxD+jgamNLM64XIIfKpCjkguR9oO4+5V o1A868QxJhiFlDNmaT7fLeeFw2z5pRAoOpTyMbOVYF3q2oj2qT6yIEvGxroE0cfX pYKnCaXI/GQrx9cbRgxBM97bsepeK8MI3/aDjxrtyaYnr0wzzdJYIfuooAhC3C/c 965pioeYqxcwauJ7thHkiPkefI1Qbl1yvMrH8iSW43bh1wm2u6V+UYCBtZSk/0e7 cbh//qd78KgmjdjdHyLw =ATxJ -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches for 2.11.0-rc3 # gpg: Signature made Wed 29 Nov 2017 15:25:13 GMT # gpg: using RSA key 0x7F09B272C88F2FD6 # gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" # Primary key fingerprint: DC3D EB15 9A9A F95D 3D74 56FE 7F09 B272 C88F 2FD6 * remotes/kevin/tags/for-upstream: block/nfs: fix nfs_client_open for filesize greater than 1TB blockjob: reimplement block_job_sleep_ns to allow cancellation blockjob: introduce block_job_do_yield blockjob: remove clock argument from block_job_sleep_ns block: Expect graph changes in bdrv_parent_drained_begin/end blockjob: Remove the job from the list earlier in block_job_unref() QAPI & interop: Clarify events emitted by 'block-job-cancel' qemu-options: Mention locking option of file driver docs: Add image locking subsection iotests: fix 075 and 078 Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
915308bc3f
@ -346,9 +346,9 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job)
|
|||||||
uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
|
uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
|
||||||
job->bytes_read);
|
job->bytes_read);
|
||||||
job->bytes_read = 0;
|
job->bytes_read = 0;
|
||||||
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&job->common, delay_ns);
|
||||||
} else {
|
} else {
|
||||||
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
|
block_job_sleep_ns(&job->common, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
if (block_job_is_cancelled(&job->common)) {
|
||||||
|
@ -174,7 +174,7 @@ static void coroutine_fn commit_run(void *opaque)
|
|||||||
/* Note that even when no rate limit is applied we need to yield
|
/* Note that even when no rate limit is applied we need to yield
|
||||||
* with no pending I/O here so that bdrv_drain_all() returns.
|
* with no pending I/O here so that bdrv_drain_all() returns.
|
||||||
*/
|
*/
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&s->common, delay_ns);
|
||||||
if (block_job_is_cancelled(&s->common)) {
|
if (block_job_is_cancelled(&s->common)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,9 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
|
|||||||
|
|
||||||
void bdrv_parent_drained_begin(BlockDriverState *bs)
|
void bdrv_parent_drained_begin(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BdrvChild *c;
|
BdrvChild *c, *next;
|
||||||
|
|
||||||
QLIST_FOREACH(c, &bs->parents, next_parent) {
|
QLIST_FOREACH_SAFE(c, &bs->parents, next_parent, next) {
|
||||||
if (c->role->drained_begin) {
|
if (c->role->drained_begin) {
|
||||||
c->role->drained_begin(c);
|
c->role->drained_begin(c);
|
||||||
}
|
}
|
||||||
@ -53,9 +53,9 @@ void bdrv_parent_drained_begin(BlockDriverState *bs)
|
|||||||
|
|
||||||
void bdrv_parent_drained_end(BlockDriverState *bs)
|
void bdrv_parent_drained_end(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BdrvChild *c;
|
BdrvChild *c, *next;
|
||||||
|
|
||||||
QLIST_FOREACH(c, &bs->parents, next_parent) {
|
QLIST_FOREACH_SAFE(c, &bs->parents, next_parent, next) {
|
||||||
if (c->role->drained_end) {
|
if (c->role->drained_end) {
|
||||||
c->role->drained_end(c);
|
c->role->drained_end(c);
|
||||||
}
|
}
|
||||||
|
@ -598,7 +598,7 @@ static void mirror_throttle(MirrorBlockJob *s)
|
|||||||
|
|
||||||
if (now - s->last_pause_ns > SLICE_TIME) {
|
if (now - s->last_pause_ns > SLICE_TIME) {
|
||||||
s->last_pause_ns = now;
|
s->last_pause_ns = now;
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, 0);
|
block_job_sleep_ns(&s->common, 0);
|
||||||
} else {
|
} else {
|
||||||
block_job_pause_point(&s->common);
|
block_job_pause_point(&s->common);
|
||||||
}
|
}
|
||||||
@ -870,13 +870,13 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
ret = 0;
|
ret = 0;
|
||||||
trace_mirror_before_sleep(s, cnt, s->synced, delay_ns);
|
trace_mirror_before_sleep(s, cnt, s->synced, delay_ns);
|
||||||
if (!s->synced) {
|
if (!s->synced) {
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&s->common, delay_ns);
|
||||||
if (block_job_is_cancelled(&s->common)) {
|
if (block_job_is_cancelled(&s->common)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (!should_complete) {
|
} else if (!should_complete) {
|
||||||
delay_ns = (s->in_flight == 0 && cnt == 0 ? SLICE_TIME : 0);
|
delay_ns = (s->in_flight == 0 && cnt == 0 ? SLICE_TIME : 0);
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&s->common, delay_ns);
|
||||||
}
|
}
|
||||||
s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* QEMU Block driver for native access to files on NFS shares
|
* QEMU Block driver for native access to files on NFS shares
|
||||||
*
|
*
|
||||||
* Copyright (c) 2014-2016 Peter Lieven <pl@kamp.de>
|
* Copyright (c) 2014-2017 Peter Lieven <pl@kamp.de>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -496,7 +496,7 @@ out:
|
|||||||
static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
||||||
int flags, int open_flags, Error **errp)
|
int flags, int open_flags, Error **errp)
|
||||||
{
|
{
|
||||||
int ret = -EINVAL;
|
int64_t ret = -EINVAL;
|
||||||
QemuOpts *opts = NULL;
|
QemuOpts *opts = NULL;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@ -686,8 +686,7 @@ static QemuOptsList nfs_create_opts = {
|
|||||||
|
|
||||||
static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
|
static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int64_t ret, total_size;
|
||||||
int64_t total_size = 0;
|
|
||||||
NFSClient *client = g_new0(NFSClient, 1);
|
NFSClient *client = g_new0(NFSClient, 1);
|
||||||
QDict *options = NULL;
|
QDict *options = NULL;
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ static void coroutine_fn stream_run(void *opaque)
|
|||||||
/* Note that even when no rate limit is applied we need to yield
|
/* Note that even when no rate limit is applied we need to yield
|
||||||
* with no pending I/O here so that bdrv_drain_all() returns.
|
* with no pending I/O here so that bdrv_drain_all() returns.
|
||||||
*/
|
*/
|
||||||
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&s->common, delay_ns);
|
||||||
if (block_job_is_cancelled(&s->common)) {
|
if (block_job_is_cancelled(&s->common)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
84
blockjob.c
84
blockjob.c
@ -37,6 +37,26 @@
|
|||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "qapi-event.h"
|
#include "qapi-event.h"
|
||||||
|
|
||||||
|
/* Right now, this mutex is only needed to synchronize accesses to job->busy
|
||||||
|
* and job->sleep_timer, such as concurrent calls to block_job_do_yield and
|
||||||
|
* block_job_enter. */
|
||||||
|
static QemuMutex block_job_mutex;
|
||||||
|
|
||||||
|
static void block_job_lock(void)
|
||||||
|
{
|
||||||
|
qemu_mutex_lock(&block_job_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void block_job_unlock(void)
|
||||||
|
{
|
||||||
|
qemu_mutex_unlock(&block_job_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __attribute__((__constructor__)) block_job_init(void)
|
||||||
|
{
|
||||||
|
qemu_mutex_init(&block_job_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
static void block_job_event_cancelled(BlockJob *job);
|
static void block_job_event_cancelled(BlockJob *job);
|
||||||
static void block_job_event_completed(BlockJob *job, const char *msg);
|
static void block_job_event_completed(BlockJob *job, const char *msg);
|
||||||
|
|
||||||
@ -152,6 +172,7 @@ void block_job_unref(BlockJob *job)
|
|||||||
{
|
{
|
||||||
if (--job->refcnt == 0) {
|
if (--job->refcnt == 0) {
|
||||||
BlockDriverState *bs = blk_bs(job->blk);
|
BlockDriverState *bs = blk_bs(job->blk);
|
||||||
|
QLIST_REMOVE(job, job_list);
|
||||||
bs->job = NULL;
|
bs->job = NULL;
|
||||||
block_job_remove_all_bdrv(job);
|
block_job_remove_all_bdrv(job);
|
||||||
blk_remove_aio_context_notifier(job->blk,
|
blk_remove_aio_context_notifier(job->blk,
|
||||||
@ -160,7 +181,7 @@ void block_job_unref(BlockJob *job)
|
|||||||
blk_unref(job->blk);
|
blk_unref(job->blk);
|
||||||
error_free(job->blocker);
|
error_free(job->blocker);
|
||||||
g_free(job->id);
|
g_free(job->id);
|
||||||
QLIST_REMOVE(job, job_list);
|
assert(!timer_pending(&job->sleep_timer));
|
||||||
g_free(job);
|
g_free(job);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,6 +308,13 @@ static void coroutine_fn block_job_co_entry(void *opaque)
|
|||||||
job->driver->start(job);
|
job->driver->start(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void block_job_sleep_timer_cb(void *opaque)
|
||||||
|
{
|
||||||
|
BlockJob *job = opaque;
|
||||||
|
|
||||||
|
block_job_enter(job);
|
||||||
|
}
|
||||||
|
|
||||||
void block_job_start(BlockJob *job)
|
void block_job_start(BlockJob *job)
|
||||||
{
|
{
|
||||||
assert(job && !block_job_started(job) && job->paused &&
|
assert(job && !block_job_started(job) && job->paused &&
|
||||||
@ -556,7 +584,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
|
|||||||
info->type = g_strdup(BlockJobType_str(job->driver->job_type));
|
info->type = g_strdup(BlockJobType_str(job->driver->job_type));
|
||||||
info->device = g_strdup(job->id);
|
info->device = g_strdup(job->id);
|
||||||
info->len = job->len;
|
info->len = job->len;
|
||||||
info->busy = job->busy;
|
info->busy = atomic_read(&job->busy);
|
||||||
info->paused = job->pause_count > 0;
|
info->paused = job->pause_count > 0;
|
||||||
info->offset = job->offset;
|
info->offset = job->offset;
|
||||||
info->speed = job->speed;
|
info->speed = job->speed;
|
||||||
@ -664,6 +692,9 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
|
|||||||
job->paused = true;
|
job->paused = true;
|
||||||
job->pause_count = 1;
|
job->pause_count = 1;
|
||||||
job->refcnt = 1;
|
job->refcnt = 1;
|
||||||
|
aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
|
||||||
|
QEMU_CLOCK_REALTIME, SCALE_NS,
|
||||||
|
block_job_sleep_timer_cb, job);
|
||||||
|
|
||||||
error_setg(&job->blocker, "block device is in use by block job: %s",
|
error_setg(&job->blocker, "block device is in use by block job: %s",
|
||||||
BlockJobType_str(driver->job_type));
|
BlockJobType_str(driver->job_type));
|
||||||
@ -729,6 +760,26 @@ static bool block_job_should_pause(BlockJob *job)
|
|||||||
return job->pause_count > 0;
|
return job->pause_count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Yield, and schedule a timer to reenter the coroutine after @ns nanoseconds.
|
||||||
|
* Reentering the job coroutine with block_job_enter() before the timer has
|
||||||
|
* expired is allowed and cancels the timer.
|
||||||
|
*
|
||||||
|
* If @ns is (uint64_t) -1, no timer is scheduled and block_job_enter() must be
|
||||||
|
* called explicitly. */
|
||||||
|
static void block_job_do_yield(BlockJob *job, uint64_t ns)
|
||||||
|
{
|
||||||
|
block_job_lock();
|
||||||
|
if (ns != -1) {
|
||||||
|
timer_mod(&job->sleep_timer, ns);
|
||||||
|
}
|
||||||
|
job->busy = false;
|
||||||
|
block_job_unlock();
|
||||||
|
qemu_coroutine_yield();
|
||||||
|
|
||||||
|
/* Set by block_job_enter before re-entering the coroutine. */
|
||||||
|
assert(job->busy);
|
||||||
|
}
|
||||||
|
|
||||||
void coroutine_fn block_job_pause_point(BlockJob *job)
|
void coroutine_fn block_job_pause_point(BlockJob *job)
|
||||||
{
|
{
|
||||||
assert(job && block_job_started(job));
|
assert(job && block_job_started(job));
|
||||||
@ -746,9 +797,7 @@ void coroutine_fn block_job_pause_point(BlockJob *job)
|
|||||||
|
|
||||||
if (block_job_should_pause(job) && !block_job_is_cancelled(job)) {
|
if (block_job_should_pause(job) && !block_job_is_cancelled(job)) {
|
||||||
job->paused = true;
|
job->paused = true;
|
||||||
job->busy = false;
|
block_job_do_yield(job, -1);
|
||||||
qemu_coroutine_yield(); /* wait for block_job_resume() */
|
|
||||||
job->busy = true;
|
|
||||||
job->paused = false;
|
job->paused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -778,9 +827,17 @@ void block_job_enter(BlockJob *job)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!job->busy) {
|
block_job_lock();
|
||||||
bdrv_coroutine_enter(blk_bs(job->blk), job->co);
|
if (job->busy) {
|
||||||
|
block_job_unlock();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(!job->deferred_to_main_loop);
|
||||||
|
timer_del(&job->sleep_timer);
|
||||||
|
job->busy = true;
|
||||||
|
block_job_unlock();
|
||||||
|
aio_co_wake(job->co);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool block_job_is_cancelled(BlockJob *job)
|
bool block_job_is_cancelled(BlockJob *job)
|
||||||
@ -788,7 +845,7 @@ bool block_job_is_cancelled(BlockJob *job)
|
|||||||
return job->cancelled;
|
return job->cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void block_job_sleep_ns(BlockJob *job, QEMUClockType type, int64_t ns)
|
void block_job_sleep_ns(BlockJob *job, int64_t ns)
|
||||||
{
|
{
|
||||||
assert(job->busy);
|
assert(job->busy);
|
||||||
|
|
||||||
@ -797,13 +854,8 @@ void block_job_sleep_ns(BlockJob *job, QEMUClockType type, int64_t ns)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We need to leave job->busy set here, because when we have
|
|
||||||
* put a coroutine to 'sleep', we have scheduled it to run in
|
|
||||||
* the future. We cannot enter that same coroutine again before
|
|
||||||
* it wakes and runs, otherwise we risk double-entry or entry after
|
|
||||||
* completion. */
|
|
||||||
if (!block_job_should_pause(job)) {
|
if (!block_job_should_pause(job)) {
|
||||||
co_aio_sleep_ns(blk_get_aio_context(job->blk), type, ns);
|
block_job_do_yield(job, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
block_job_pause_point(job);
|
block_job_pause_point(job);
|
||||||
@ -818,11 +870,9 @@ void block_job_yield(BlockJob *job)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
job->busy = false;
|
|
||||||
if (!block_job_should_pause(job)) {
|
if (!block_job_should_pause(job)) {
|
||||||
qemu_coroutine_yield();
|
block_job_do_yield(job, -1);
|
||||||
}
|
}
|
||||||
job->busy = true;
|
|
||||||
|
|
||||||
block_job_pause_point(job);
|
block_job_pause_point(job);
|
||||||
}
|
}
|
||||||
|
@ -506,26 +506,40 @@ Again, given our familiar disk image chain::
|
|||||||
|
|
||||||
[A] <-- [B] <-- [C] <-- [D]
|
[A] <-- [B] <-- [C] <-- [D]
|
||||||
|
|
||||||
The ``drive-mirror`` (and its newer equivalent ``blockdev-mirror``) allows
|
The ``drive-mirror`` (and its newer equivalent ``blockdev-mirror``)
|
||||||
you to copy data from the entire chain into a single target image (which
|
allows you to copy data from the entire chain into a single target image
|
||||||
can be located on a different host).
|
(which can be located on a different host), [E].
|
||||||
|
|
||||||
Once a 'mirror' job has started, there are two possible actions while a
|
.. note::
|
||||||
``drive-mirror`` job is active:
|
|
||||||
|
|
||||||
(1) Issuing the command ``block-job-cancel`` after it emits the event
|
When you cancel an in-progress 'mirror' job *before* the source and
|
||||||
``BLOCK_JOB_CANCELLED``: will (after completing synchronization of
|
target are synchronized, ``block-job-cancel`` will emit the event
|
||||||
the content from the disk image chain to the target image, [E])
|
``BLOCK_JOB_CANCELLED``. However, note that if you cancel a
|
||||||
create a point-in-time (which is at the time of *triggering* the
|
'mirror' job *after* it has indicated (via the event
|
||||||
cancel command) copy, contained in image [E], of the the entire disk
|
``BLOCK_JOB_READY``) that the source and target have reached
|
||||||
|
synchronization, then the event emitted by ``block-job-cancel``
|
||||||
|
changes to ``BLOCK_JOB_COMPLETED``.
|
||||||
|
|
||||||
|
Besides the 'mirror' job, the "active ``block-commit``" is the only
|
||||||
|
other block device job that emits the event ``BLOCK_JOB_READY``.
|
||||||
|
The rest of the block device jobs ('stream', "non-active
|
||||||
|
``block-commit``", and 'backup') end automatically.
|
||||||
|
|
||||||
|
So there are two possible actions to take, after a 'mirror' job has
|
||||||
|
emitted the event ``BLOCK_JOB_READY``, indicating that the source and
|
||||||
|
target have reached synchronization:
|
||||||
|
|
||||||
|
(1) Issuing the command ``block-job-cancel`` (after it emits the event
|
||||||
|
``BLOCK_JOB_COMPLETED``) will create a point-in-time (which is at
|
||||||
|
the time of *triggering* the cancel command) copy of the entire disk
|
||||||
image chain (or only the top-most image, depending on the ``sync``
|
image chain (or only the top-most image, depending on the ``sync``
|
||||||
mode).
|
mode), contained in the target image [E]. One use case for this is
|
||||||
|
live VM migration with non-shared storage.
|
||||||
|
|
||||||
(2) Issuing the command ``block-job-complete`` after it emits the event
|
(2) Issuing the command ``block-job-complete`` (after it emits the event
|
||||||
``BLOCK_JOB_COMPLETED``: will, after completing synchronization of
|
``BLOCK_JOB_COMPLETED``) will adjust the guest device (i.e. live
|
||||||
the content, adjust the guest device (i.e. live QEMU) to point to
|
QEMU) to point to the target image, [E], causing all the new writes
|
||||||
the target image, and, causing all the new writes from this point on
|
from this point on to happen there.
|
||||||
to happen there. One use case for this is live storage migration.
|
|
||||||
|
|
||||||
About synchronization modes: The synchronization mode determines
|
About synchronization modes: The synchronization mode determines
|
||||||
*which* part of the disk image chain will be copied to the target.
|
*which* part of the disk image chain will be copied to the target.
|
||||||
|
@ -785,6 +785,42 @@ warning: ssh server @code{ssh.example.com:22} does not support fsync
|
|||||||
With sufficiently new versions of libssh2 and OpenSSH, @code{fsync} is
|
With sufficiently new versions of libssh2 and OpenSSH, @code{fsync} is
|
||||||
supported.
|
supported.
|
||||||
|
|
||||||
|
@node disk_image_locking
|
||||||
|
@subsection Disk image file locking
|
||||||
|
|
||||||
|
By default, QEMU tries to protect image files from unexpected concurrent
|
||||||
|
access, as long as it's supported by the block protocol driver and host
|
||||||
|
operating system. If multiple QEMU processes (including QEMU emulators and
|
||||||
|
utilities) try to open the same image with conflicting accessing modes, all but
|
||||||
|
the first one will get an error.
|
||||||
|
|
||||||
|
This feature is currently supported by the file protocol on Linux with the Open
|
||||||
|
File Descriptor (OFD) locking API, and can be configured to fall back to POSIX
|
||||||
|
locking if the POSIX host doesn't support Linux OFD locking.
|
||||||
|
|
||||||
|
To explicitly enable image locking, specify "locking=on" in the file protocol
|
||||||
|
driver options. If OFD locking is not possible, a warning will be printed and
|
||||||
|
the POSIX locking API will be used. In this case there is a risk that the lock
|
||||||
|
will get silently lost when doing hot plugging and block jobs, due to the
|
||||||
|
shortcomings of the POSIX locking API.
|
||||||
|
|
||||||
|
QEMU transparently handles lock handover during shared storage migration. For
|
||||||
|
shared virtual disk images between multiple VMs, the "share-rw" device option
|
||||||
|
should be used.
|
||||||
|
|
||||||
|
Alternatively, locking can be fully disabled by "locking=off" block device
|
||||||
|
option. In the command line, the option is usually in the form of
|
||||||
|
"file.locking=off" as the protocol driver is normally placed as a "file" child
|
||||||
|
under a format driver. For example:
|
||||||
|
|
||||||
|
@code{-blockdev driver=qcow2,file.filename=/path/to/image,file.locking=off,file.driver=file}
|
||||||
|
|
||||||
|
To check if image locking is active, check the output of the "lslocks" command
|
||||||
|
on host and see if there are locks held by the QEMU process on the image file.
|
||||||
|
More than one byte could be locked by the QEMU instance, each byte of which
|
||||||
|
reflects a particular permission that is acquired or protected by the running
|
||||||
|
block driver.
|
||||||
|
|
||||||
@c man end
|
@c man end
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
|
@ -77,7 +77,7 @@ typedef struct BlockJob {
|
|||||||
/**
|
/**
|
||||||
* Set to false by the job while the coroutine has yielded and may be
|
* Set to false by the job while the coroutine has yielded and may be
|
||||||
* re-entered by block_job_enter(). There may still be I/O or event loop
|
* re-entered by block_job_enter(). There may still be I/O or event loop
|
||||||
* activity pending.
|
* activity pending. Accessed under block_job_mutex (in blockjob.c).
|
||||||
*/
|
*/
|
||||||
bool busy;
|
bool busy;
|
||||||
|
|
||||||
@ -135,6 +135,12 @@ typedef struct BlockJob {
|
|||||||
*/
|
*/
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer that is used by @block_job_sleep_ns. Accessed under
|
||||||
|
* block_job_mutex (in blockjob.c).
|
||||||
|
*/
|
||||||
|
QEMUTimer sleep_timer;
|
||||||
|
|
||||||
/** Non-NULL if this job is part of a transaction */
|
/** Non-NULL if this job is part of a transaction */
|
||||||
BlockJobTxn *txn;
|
BlockJobTxn *txn;
|
||||||
QLIST_ENTRY(BlockJob) txn_list;
|
QLIST_ENTRY(BlockJob) txn_list;
|
||||||
|
@ -139,14 +139,13 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
|
|||||||
/**
|
/**
|
||||||
* block_job_sleep_ns:
|
* block_job_sleep_ns:
|
||||||
* @job: The job that calls the function.
|
* @job: The job that calls the function.
|
||||||
* @clock: The clock to sleep on.
|
|
||||||
* @ns: How many nanoseconds to stop for.
|
* @ns: How many nanoseconds to stop for.
|
||||||
*
|
*
|
||||||
* Put the job to sleep (assuming that it wasn't canceled) for @ns
|
* Put the job to sleep (assuming that it wasn't canceled) for @ns
|
||||||
* nanoseconds. Canceling the job will not interrupt the wait, so the
|
* %QEMU_CLOCK_REALTIME nanoseconds. Canceling the job will immediately
|
||||||
* cancel will not process until the coroutine wakes up.
|
* interrupt the wait.
|
||||||
*/
|
*/
|
||||||
void block_job_sleep_ns(BlockJob *job, QEMUClockType type, int64_t ns);
|
void block_job_sleep_ns(BlockJob *job, int64_t ns);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* block_job_yield:
|
* block_job_yield:
|
||||||
|
@ -2065,6 +2065,12 @@
|
|||||||
# BLOCK_JOB_CANCELLED event. Before that happens the job is still visible when
|
# BLOCK_JOB_CANCELLED event. Before that happens the job is still visible when
|
||||||
# enumerated using query-block-jobs.
|
# enumerated using query-block-jobs.
|
||||||
#
|
#
|
||||||
|
# Note that if you issue 'block-job-cancel' after 'drive-mirror' has indicated
|
||||||
|
# (via the event BLOCK_JOB_READY) that the source and destination are
|
||||||
|
# synchronized, then the event triggered by this command changes to
|
||||||
|
# BLOCK_JOB_COMPLETED, to indicate that the mirroring has ended and the
|
||||||
|
# destination now has a point-in-time copy tied to the time of the cancellation.
|
||||||
|
#
|
||||||
# For streaming, the image file retains its backing file unless the streaming
|
# 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 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
|
# operation can be started at a later time to finish copying all data from the
|
||||||
|
@ -405,6 +405,7 @@ encrypted disk images.
|
|||||||
* disk_images_iscsi:: iSCSI LUNs
|
* disk_images_iscsi:: iSCSI LUNs
|
||||||
* disk_images_gluster:: GlusterFS disk images
|
* disk_images_gluster:: GlusterFS disk images
|
||||||
* disk_images_ssh:: Secure Shell (ssh) disk images
|
* disk_images_ssh:: Secure Shell (ssh) disk images
|
||||||
|
* disk_image_locking:: Disk image file locking
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
@node disk_images_quickstart
|
@node disk_images_quickstart
|
||||||
|
@ -693,6 +693,10 @@ This is the protocol-level block driver for accessing regular files.
|
|||||||
The path to the image file in the local filesystem
|
The path to the image file in the local filesystem
|
||||||
@item aio
|
@item aio
|
||||||
Specifies the AIO backend (threads/native, default: threads)
|
Specifies the AIO backend (threads/native, default: threads)
|
||||||
|
@item locking
|
||||||
|
Specifies whether the image file is protected with Linux OFD / POSIX locks. The
|
||||||
|
default is to use the Linux Open File Descriptor API if available, otherwise no
|
||||||
|
lock is applied. (auto/on/off, default: auto)
|
||||||
@end table
|
@end table
|
||||||
Example:
|
Example:
|
||||||
@example
|
@example
|
||||||
|
@ -48,56 +48,56 @@ offsets_offset=136
|
|||||||
echo
|
echo
|
||||||
echo "== check that the first sector can be read =="
|
echo "== check that the first sector can be read =="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
$QEMU_IO -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== check that the last sector can be read =="
|
echo "== check that the last sector can be read =="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
$QEMU_IO -c "read $((1024 * 1024 - 512)) 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read $((1024 * 1024 - 512)) 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== block_size must be a multiple of 512 =="
|
echo "== block_size must be a multiple of 512 =="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
poke_file "$TEST_IMG" "$block_size_offset" "\x00\x00\x02\x01"
|
poke_file "$TEST_IMG" "$block_size_offset" "\x00\x00\x02\x01"
|
||||||
$QEMU_IO -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== block_size cannot be zero =="
|
echo "== block_size cannot be zero =="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
poke_file "$TEST_IMG" "$block_size_offset" "\x00\x00\x00\x00"
|
poke_file "$TEST_IMG" "$block_size_offset" "\x00\x00\x00\x00"
|
||||||
$QEMU_IO -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== huge block_size ==="
|
echo "== huge block_size ==="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
poke_file "$TEST_IMG" "$block_size_offset" "\xff\xff\xfe\x00"
|
poke_file "$TEST_IMG" "$block_size_offset" "\xff\xff\xfe\x00"
|
||||||
$QEMU_IO -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== offsets_size overflow ==="
|
echo "== offsets_size overflow ==="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
poke_file "$TEST_IMG" "$n_blocks_offset" "\xff\xff\xff\xff"
|
poke_file "$TEST_IMG" "$n_blocks_offset" "\xff\xff\xff\xff"
|
||||||
$QEMU_IO -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== refuse images that require too many offsets ==="
|
echo "== refuse images that require too many offsets ==="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
poke_file "$TEST_IMG" "$n_blocks_offset" "\x04\x00\x00\x01"
|
poke_file "$TEST_IMG" "$n_blocks_offset" "\x04\x00\x00\x01"
|
||||||
$QEMU_IO -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== refuse images with non-monotonically increasing offsets =="
|
echo "== refuse images with non-monotonically increasing offsets =="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
poke_file "$TEST_IMG" "$offsets_offset" "\x00\x00\x00\x00\xff\xff\xff\xff"
|
poke_file "$TEST_IMG" "$offsets_offset" "\x00\x00\x00\x00\xff\xff\xff\xff"
|
||||||
poke_file "$TEST_IMG" $((offsets_offset + 8)) "\x00\x00\x00\x00\xff\xfe\x00\x00"
|
poke_file "$TEST_IMG" $((offsets_offset + 8)) "\x00\x00\x00\x00\xff\xfe\x00\x00"
|
||||||
$QEMU_IO -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== refuse images with invalid compressed block size =="
|
echo "== refuse images with invalid compressed block size =="
|
||||||
_use_sample_img simple-pattern.cloop.bz2
|
_use_sample_img simple-pattern.cloop.bz2
|
||||||
poke_file "$TEST_IMG" "$offsets_offset" "\x00\x00\x00\x00\x00\x00\x00\x00"
|
poke_file "$TEST_IMG" "$offsets_offset" "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||||
poke_file "$TEST_IMG" $((offsets_offset + 8)) "\xff\xff\xff\xff\xff\xff\xff\xff"
|
poke_file "$TEST_IMG" $((offsets_offset + 8)) "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||||
$QEMU_IO -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -r -c "read 0 512" $TEST_IMG 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
# success, all done
|
# success, all done
|
||||||
echo "*** done"
|
echo "*** done"
|
||||||
|
@ -48,41 +48,41 @@ disk_size_offset=$((0x58))
|
|||||||
echo
|
echo
|
||||||
echo "== Read from a valid image =="
|
echo "== Read from a valid image =="
|
||||||
_use_sample_img empty.bochs.bz2
|
_use_sample_img empty.bochs.bz2
|
||||||
{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
{ $QEMU_IO -r -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== Negative catalog size =="
|
echo "== Negative catalog size =="
|
||||||
_use_sample_img empty.bochs.bz2
|
_use_sample_img empty.bochs.bz2
|
||||||
poke_file "$TEST_IMG" "$catalog_size_offset" "\xff\xff\xff\xff"
|
poke_file "$TEST_IMG" "$catalog_size_offset" "\xff\xff\xff\xff"
|
||||||
{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
{ $QEMU_IO -r -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== Overflow for catalog size * sizeof(uint32_t) =="
|
echo "== Overflow for catalog size * sizeof(uint32_t) =="
|
||||||
_use_sample_img empty.bochs.bz2
|
_use_sample_img empty.bochs.bz2
|
||||||
poke_file "$TEST_IMG" "$catalog_size_offset" "\x00\x00\x00\x40"
|
poke_file "$TEST_IMG" "$catalog_size_offset" "\x00\x00\x00\x40"
|
||||||
{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
{ $QEMU_IO -r -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== Too small catalog bitmap for image size =="
|
echo "== Too small catalog bitmap for image size =="
|
||||||
_use_sample_img empty.bochs.bz2
|
_use_sample_img empty.bochs.bz2
|
||||||
poke_file "$TEST_IMG" "$disk_size_offset" "\x00\xc0\x0f\x00\x00\x00\x00\x7f"
|
poke_file "$TEST_IMG" "$disk_size_offset" "\x00\xc0\x0f\x00\x00\x00\x00\x7f"
|
||||||
{ $QEMU_IO -c "read 2T 4k" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
{ $QEMU_IO -r -c "read 2T 4k" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
_use_sample_img empty.bochs.bz2
|
_use_sample_img empty.bochs.bz2
|
||||||
poke_file "$TEST_IMG" "$catalog_size_offset" "\x10\x00\x00\x00"
|
poke_file "$TEST_IMG" "$catalog_size_offset" "\x10\x00\x00\x00"
|
||||||
{ $QEMU_IO -c "read 0xfbe00 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
{ $QEMU_IO -r -c "read 0xfbe00 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== Negative extent size =="
|
echo "== Negative extent size =="
|
||||||
_use_sample_img empty.bochs.bz2
|
_use_sample_img empty.bochs.bz2
|
||||||
poke_file "$TEST_IMG" "$extent_size_offset" "\x00\x00\x00\x80"
|
poke_file "$TEST_IMG" "$extent_size_offset" "\x00\x00\x00\x80"
|
||||||
{ $QEMU_IO -c "read 768k 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
{ $QEMU_IO -r -c "read 768k 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== Zero extent size =="
|
echo "== Zero extent size =="
|
||||||
_use_sample_img empty.bochs.bz2
|
_use_sample_img empty.bochs.bz2
|
||||||
poke_file "$TEST_IMG" "$extent_size_offset" "\x00\x00\x00\x00"
|
poke_file "$TEST_IMG" "$extent_size_offset" "\x00\x00\x00\x00"
|
||||||
{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
{ $QEMU_IO -r -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
# success, all done
|
# success, all done
|
||||||
echo "*** done"
|
echo "*** done"
|
||||||
|
@ -44,7 +44,7 @@ static void coroutine_fn test_block_job_run(void *opaque)
|
|||||||
|
|
||||||
while (s->iterations--) {
|
while (s->iterations--) {
|
||||||
if (s->use_timer) {
|
if (s->use_timer) {
|
||||||
block_job_sleep_ns(job, QEMU_CLOCK_REALTIME, 0);
|
block_job_sleep_ns(job, 0);
|
||||||
} else {
|
} else {
|
||||||
block_job_yield(job);
|
block_job_yield(job);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user