mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-29 06:20:26 +00:00
Block layer patches:
- Switch AIO/callback based block drivers to a byte-based interface - Block jobs: Expose error string via query-block-jobs - Block job cleanups and fixes - hmp: Allow using a qdev id in block_set_io_throttle -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJa+v22AAoJEH8JsnLIjy/Wy5kP/21fPSvmyxMsSQ5lF5hlkodr qNFIV6EuQ46YXSr4KzK2Dw88YR18nI5SlMd6mzWF9qx7WhjTMeHhARG9G497cQty yDb3Y6dwiuhVndWLMzj/590miqk5TnJvFx5ii88oEnsrbcjKmTY78KMkl/q1bHSp qL7sBhI3zPol+y28mvILPXgKsqnabvS/cmsQJCISUfSdFsnsxXVABUPI/WKe1ecs UE3tl3cDA/0F8TYDerPUX1RZLJcr7yoXc91ieVWrzug4SStY3HZqWT8SShe2igN0 w5eWshUBhccHKeiqKx8vdXN7MzNP4v5H2lstTdqV/zDQEXO9vqF1X91zaRBJNb+o A4M3lZU/U3xideBo7Hvp4euJ5f6ZKNswpeIC3Ppky788Q+HU/d+cQF+eZUxm+t8y vVtixTToSt52dIaAPMsssWCtVwkS4IFO9RLXJeRs94XR3ocrsSdJNOQPTodWafjF BXIF4wyKlPvVMzisvpj6jsjVR4Oq7J7P+EXq+hAMbio9WlsXssWu0ZedH8oV+mKl UkEB5TgRAaz8MpYNCCEhUwdmHLwlc/PaPN+BAlvQCnDWMtfiFTys6Sh9N9vv/c16 HISDkR8sf7gPm3VLnt2fPoVOQMU3Gjtany+RySXfn1AzCRG3tPzdS/Ay+s1WpoYv 7F2Pm9QLepP7xRB48yD3 =JFBS -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches: - Switch AIO/callback based block drivers to a byte-based interface - Block jobs: Expose error string via query-block-jobs - Block job cleanups and fixes - hmp: Allow using a qdev id in block_set_io_throttle # gpg: Signature made Tue 15 May 2018 16:33:10 BST # gpg: using RSA key 7F09B272C88F2FD6 # 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: (37 commits) iotests: Add test for -U/force-share conflicts qemu-img: Use only string options in img_open_opts qemu-io: Use purely string blockdev options block: Document BDRV_REQ_WRITE_UNCHANGED support qemu-img: Check post-truncation size iotests: Add test for COR across nodes iotests: Copy 197 for COR filter driver iotests: Clean up wrap image in 197 block: Support BDRV_REQ_WRITE_UNCHANGED in filters block/quorum: Support BDRV_REQ_WRITE_UNCHANGED block: Set BDRV_REQ_WRITE_UNCHANGED for COR writes block: Add BDRV_REQ_WRITE_UNCHANGED flag block: BLK_PERM_WRITE includes ..._UNCHANGED block: Add COR filter driver iotests: Skip 181 and 201 without userfaultfd iotests: Add failure matching to common.qemu docs: Document the new default sizes of the qcow2 caches qcow2: Give the refcount cache the minimum possible size by default specs/qcow2: Clarify that compressed clusters have the COPIED bit reset Fix error message about compressed clusters with OFLAG_COPIED ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
c416eecea5
@ -26,7 +26,7 @@ block-obj-y += accounting.o dirty-bitmap.o
|
|||||||
block-obj-y += write-threshold.o
|
block-obj-y += write-threshold.o
|
||||||
block-obj-y += backup.o
|
block-obj-y += backup.o
|
||||||
block-obj-$(CONFIG_REPLICATION) += replication.o
|
block-obj-$(CONFIG_REPLICATION) += replication.o
|
||||||
block-obj-y += throttle.o
|
block-obj-y += throttle.o copy-on-read.o
|
||||||
|
|
||||||
block-obj-y += crypto.o
|
block-obj-y += crypto.o
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
#include "qemu/error-report.h"
|
#include "qemu/error-report.h"
|
||||||
|
|
||||||
#define BACKUP_CLUSTER_SIZE_DEFAULT (1 << 16)
|
#define BACKUP_CLUSTER_SIZE_DEFAULT (1 << 16)
|
||||||
#define SLICE_TIME 100000000ULL /* ns */
|
|
||||||
|
|
||||||
typedef struct BackupBlockJob {
|
typedef struct BackupBlockJob {
|
||||||
BlockJob common;
|
BlockJob common;
|
||||||
@ -35,10 +34,10 @@ typedef struct BackupBlockJob {
|
|||||||
/* bitmap for sync=incremental */
|
/* bitmap for sync=incremental */
|
||||||
BdrvDirtyBitmap *sync_bitmap;
|
BdrvDirtyBitmap *sync_bitmap;
|
||||||
MirrorSyncMode sync_mode;
|
MirrorSyncMode sync_mode;
|
||||||
RateLimit limit;
|
|
||||||
BlockdevOnError on_source_error;
|
BlockdevOnError on_source_error;
|
||||||
BlockdevOnError on_target_error;
|
BlockdevOnError on_target_error;
|
||||||
CoRwlock flush_rwlock;
|
CoRwlock flush_rwlock;
|
||||||
|
uint64_t len;
|
||||||
uint64_t bytes_read;
|
uint64_t bytes_read;
|
||||||
int64_t cluster_size;
|
int64_t cluster_size;
|
||||||
bool compress;
|
bool compress;
|
||||||
@ -48,6 +47,8 @@ typedef struct BackupBlockJob {
|
|||||||
HBitmap *copy_bitmap;
|
HBitmap *copy_bitmap;
|
||||||
} BackupBlockJob;
|
} BackupBlockJob;
|
||||||
|
|
||||||
|
static const BlockJobDriver backup_job_driver;
|
||||||
|
|
||||||
/* See if in-flight requests overlap and wait for them to complete */
|
/* See if in-flight requests overlap and wait for them to complete */
|
||||||
static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
|
static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
|
||||||
int64_t start,
|
int64_t start,
|
||||||
@ -118,7 +119,7 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
|||||||
|
|
||||||
trace_backup_do_cow_process(job, start);
|
trace_backup_do_cow_process(job, start);
|
||||||
|
|
||||||
n = MIN(job->cluster_size, job->common.len - start);
|
n = MIN(job->cluster_size, job->len - start);
|
||||||
|
|
||||||
if (!bounce_buffer) {
|
if (!bounce_buffer) {
|
||||||
bounce_buffer = blk_blockalign(blk, job->cluster_size);
|
bounce_buffer = blk_blockalign(blk, job->cluster_size);
|
||||||
@ -159,7 +160,7 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
|||||||
* offset field is an opaque progress value, it is not a disk offset.
|
* offset field is an opaque progress value, it is not a disk offset.
|
||||||
*/
|
*/
|
||||||
job->bytes_read += n;
|
job->bytes_read += n;
|
||||||
job->common.offset += n;
|
block_job_progress_update(&job->common, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@ -190,17 +191,6 @@ static int coroutine_fn backup_before_write_notify(
|
|||||||
return backup_do_cow(job, req->offset, req->bytes, NULL, true);
|
return backup_do_cow(job, req->offset, req->bytes, NULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|
||||||
{
|
|
||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
|
||||||
|
|
||||||
if (speed < 0) {
|
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
|
static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
|
||||||
{
|
{
|
||||||
BdrvDirtyBitmap *bm;
|
BdrvDirtyBitmap *bm;
|
||||||
@ -253,7 +243,7 @@ void backup_do_checkpoint(BlockJob *job, Error **errp)
|
|||||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
||||||
int64_t len;
|
int64_t len;
|
||||||
|
|
||||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
assert(block_job_driver(job) == &backup_job_driver);
|
||||||
|
|
||||||
if (backup_job->sync_mode != MIRROR_SYNC_MODE_NONE) {
|
if (backup_job->sync_mode != MIRROR_SYNC_MODE_NONE) {
|
||||||
error_setg(errp, "The backup job only supports block checkpoint in"
|
error_setg(errp, "The backup job only supports block checkpoint in"
|
||||||
@ -261,7 +251,7 @@ void backup_do_checkpoint(BlockJob *job, Error **errp)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
len = DIV_ROUND_UP(backup_job->common.len, backup_job->cluster_size);
|
len = DIV_ROUND_UP(backup_job->len, backup_job->cluster_size);
|
||||||
hbitmap_set(backup_job->copy_bitmap, 0, len);
|
hbitmap_set(backup_job->copy_bitmap, 0, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +261,7 @@ void backup_wait_for_overlapping_requests(BlockJob *job, int64_t offset,
|
|||||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
||||||
int64_t start, end;
|
int64_t start, end;
|
||||||
|
|
||||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
assert(block_job_driver(job) == &backup_job_driver);
|
||||||
|
|
||||||
start = QEMU_ALIGN_DOWN(offset, backup_job->cluster_size);
|
start = QEMU_ALIGN_DOWN(offset, backup_job->cluster_size);
|
||||||
end = QEMU_ALIGN_UP(offset + bytes, backup_job->cluster_size);
|
end = QEMU_ALIGN_UP(offset + bytes, backup_job->cluster_size);
|
||||||
@ -284,7 +274,7 @@ void backup_cow_request_begin(CowRequest *req, BlockJob *job,
|
|||||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
||||||
int64_t start, end;
|
int64_t start, end;
|
||||||
|
|
||||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
assert(block_job_driver(job) == &backup_job_driver);
|
||||||
|
|
||||||
start = QEMU_ALIGN_DOWN(offset, backup_job->cluster_size);
|
start = QEMU_ALIGN_DOWN(offset, backup_job->cluster_size);
|
||||||
end = QEMU_ALIGN_UP(offset + bytes, backup_job->cluster_size);
|
end = QEMU_ALIGN_UP(offset + bytes, backup_job->cluster_size);
|
||||||
@ -337,21 +327,17 @@ static void backup_complete(BlockJob *job, void *opaque)
|
|||||||
|
|
||||||
static bool coroutine_fn yield_and_check(BackupBlockJob *job)
|
static bool coroutine_fn yield_and_check(BackupBlockJob *job)
|
||||||
{
|
{
|
||||||
|
uint64_t delay_ns;
|
||||||
|
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
if (block_job_is_cancelled(&job->common)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* we need to yield so that bdrv_drain_all() returns.
|
/* We need to yield even for delay_ns = 0 so that bdrv_drain_all() can
|
||||||
* (without, VM does not reboot)
|
* return. Without a yield, the VM would not reboot. */
|
||||||
*/
|
delay_ns = block_job_ratelimit_get_delay(&job->common, job->bytes_read);
|
||||||
if (job->common.speed) {
|
|
||||||
uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
|
|
||||||
job->bytes_read);
|
|
||||||
job->bytes_read = 0;
|
job->bytes_read = 0;
|
||||||
block_job_sleep_ns(&job->common, delay_ns);
|
block_job_sleep_ns(&job->common, delay_ns);
|
||||||
} else {
|
|
||||||
block_job_sleep_ns(&job->common, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
if (block_job_is_cancelled(&job->common)) {
|
||||||
return true;
|
return true;
|
||||||
@ -420,8 +406,9 @@ static void backup_incremental_init_copy_bitmap(BackupBlockJob *job)
|
|||||||
bdrv_set_dirty_iter(dbi, next_cluster * job->cluster_size);
|
bdrv_set_dirty_iter(dbi, next_cluster * job->cluster_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
job->common.offset = job->common.len -
|
/* TODO block_job_progress_set_remaining() would make more sense */
|
||||||
hbitmap_count(job->copy_bitmap) * job->cluster_size;
|
block_job_progress_update(&job->common,
|
||||||
|
job->len - hbitmap_count(job->copy_bitmap) * job->cluster_size);
|
||||||
|
|
||||||
bdrv_dirty_iter_free(dbi);
|
bdrv_dirty_iter_free(dbi);
|
||||||
}
|
}
|
||||||
@ -437,7 +424,9 @@ static void coroutine_fn backup_run(void *opaque)
|
|||||||
QLIST_INIT(&job->inflight_reqs);
|
QLIST_INIT(&job->inflight_reqs);
|
||||||
qemu_co_rwlock_init(&job->flush_rwlock);
|
qemu_co_rwlock_init(&job->flush_rwlock);
|
||||||
|
|
||||||
nb_clusters = DIV_ROUND_UP(job->common.len, job->cluster_size);
|
nb_clusters = DIV_ROUND_UP(job->len, job->cluster_size);
|
||||||
|
block_job_progress_set_remaining(&job->common, job->len);
|
||||||
|
|
||||||
job->copy_bitmap = hbitmap_alloc(nb_clusters, 0);
|
job->copy_bitmap = hbitmap_alloc(nb_clusters, 0);
|
||||||
if (job->sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
|
if (job->sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
|
||||||
backup_incremental_init_copy_bitmap(job);
|
backup_incremental_init_copy_bitmap(job);
|
||||||
@ -461,7 +450,7 @@ static void coroutine_fn backup_run(void *opaque)
|
|||||||
ret = backup_run_incremental(job);
|
ret = backup_run_incremental(job);
|
||||||
} else {
|
} else {
|
||||||
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
||||||
for (offset = 0; offset < job->common.len;
|
for (offset = 0; offset < job->len;
|
||||||
offset += job->cluster_size) {
|
offset += job->cluster_size) {
|
||||||
bool error_is_read;
|
bool error_is_read;
|
||||||
int alloced = 0;
|
int alloced = 0;
|
||||||
@ -537,7 +526,6 @@ static const BlockJobDriver backup_job_driver = {
|
|||||||
.instance_size = sizeof(BackupBlockJob),
|
.instance_size = sizeof(BackupBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_BACKUP,
|
.job_type = BLOCK_JOB_TYPE_BACKUP,
|
||||||
.start = backup_run,
|
.start = backup_run,
|
||||||
.set_speed = backup_set_speed,
|
|
||||||
.commit = backup_commit,
|
.commit = backup_commit,
|
||||||
.abort = backup_abort,
|
.abort = backup_abort,
|
||||||
.clean = backup_clean,
|
.clean = backup_clean,
|
||||||
@ -620,7 +608,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* job->common.len is fixed, so we can't allow resize */
|
/* job->len is fixed, so we can't allow resize */
|
||||||
job = block_job_create(job_id, &backup_job_driver, txn, bs,
|
job = block_job_create(job_id, &backup_job_driver, txn, bs,
|
||||||
BLK_PERM_CONSISTENT_READ,
|
BLK_PERM_CONSISTENT_READ,
|
||||||
BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
|
BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
|
||||||
@ -676,7 +664,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
|||||||
/* Required permissions are already taken with target's blk_new() */
|
/* Required permissions are already taken with target's blk_new() */
|
||||||
block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
|
block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
|
||||||
&error_abort);
|
&error_abort);
|
||||||
job->common.len = len;
|
job->len = len;
|
||||||
|
|
||||||
return &job->common;
|
return &job->common;
|
||||||
|
|
||||||
|
@ -398,10 +398,11 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bs->supported_write_flags = BDRV_REQ_FUA &
|
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||||
bs->file->bs->supported_write_flags;
|
(BDRV_REQ_FUA & bs->file->bs->supported_write_flags);
|
||||||
bs->supported_zero_flags = (BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP) &
|
bs->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||||
bs->file->bs->supported_zero_flags;
|
((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP) &
|
||||||
|
bs->file->bs->supported_zero_flags);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
|
|
||||||
/* Set alignment overrides */
|
/* Set alignment overrides */
|
||||||
|
@ -35,6 +35,9 @@ static int blkreplay_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED;
|
||||||
|
bs->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED;
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
fail:
|
fail:
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -141,6 +141,9 @@ static int blkverify_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED;
|
||||||
|
bs->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED;
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
fail:
|
fail:
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
|
@ -1865,13 +1865,7 @@ void blk_op_unblock_all(BlockBackend *blk, Error *reason)
|
|||||||
|
|
||||||
AioContext *blk_get_aio_context(BlockBackend *blk)
|
AioContext *blk_get_aio_context(BlockBackend *blk)
|
||||||
{
|
{
|
||||||
BlockDriverState *bs = blk_bs(blk);
|
return bdrv_get_aio_context(blk_bs(blk));
|
||||||
|
|
||||||
if (bs) {
|
|
||||||
return bdrv_get_aio_context(bs);
|
|
||||||
} else {
|
|
||||||
return qemu_get_aio_context();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static AioContext *blk_aiocb_get_aio_context(BlockAIOCB *acb)
|
static AioContext *blk_aiocb_get_aio_context(BlockAIOCB *acb)
|
||||||
|
@ -31,11 +31,8 @@ enum {
|
|||||||
COMMIT_BUFFER_SIZE = 512 * 1024, /* in bytes */
|
COMMIT_BUFFER_SIZE = 512 * 1024, /* in bytes */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SLICE_TIME 100000000ULL /* ns */
|
|
||||||
|
|
||||||
typedef struct CommitBlockJob {
|
typedef struct CommitBlockJob {
|
||||||
BlockJob common;
|
BlockJob common;
|
||||||
RateLimit limit;
|
|
||||||
BlockDriverState *commit_top_bs;
|
BlockDriverState *commit_top_bs;
|
||||||
BlockBackend *top;
|
BlockBackend *top;
|
||||||
BlockBackend *base;
|
BlockBackend *base;
|
||||||
@ -146,21 +143,21 @@ static void coroutine_fn commit_run(void *opaque)
|
|||||||
int64_t n = 0; /* bytes */
|
int64_t n = 0; /* bytes */
|
||||||
void *buf = NULL;
|
void *buf = NULL;
|
||||||
int bytes_written = 0;
|
int bytes_written = 0;
|
||||||
int64_t base_len;
|
int64_t len, base_len;
|
||||||
|
|
||||||
ret = s->common.len = blk_getlength(s->top);
|
ret = len = blk_getlength(s->top);
|
||||||
|
if (len < 0) {
|
||||||
if (s->common.len < 0) {
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
block_job_progress_set_remaining(&s->common, len);
|
||||||
|
|
||||||
ret = base_len = blk_getlength(s->base);
|
ret = base_len = blk_getlength(s->base);
|
||||||
if (base_len < 0) {
|
if (base_len < 0) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (base_len < s->common.len) {
|
if (base_len < len) {
|
||||||
ret = blk_truncate(s->base, s->common.len, PREALLOC_MODE_OFF, NULL);
|
ret = blk_truncate(s->base, len, PREALLOC_MODE_OFF, NULL);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@ -168,7 +165,7 @@ static void coroutine_fn commit_run(void *opaque)
|
|||||||
|
|
||||||
buf = blk_blockalign(s->top, COMMIT_BUFFER_SIZE);
|
buf = blk_blockalign(s->top, COMMIT_BUFFER_SIZE);
|
||||||
|
|
||||||
for (offset = 0; offset < s->common.len; offset += n) {
|
for (offset = 0; offset < len; offset += n) {
|
||||||
bool copy;
|
bool copy;
|
||||||
|
|
||||||
/* 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
|
||||||
@ -198,10 +195,10 @@ static void coroutine_fn commit_run(void *opaque)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Publish progress */
|
/* Publish progress */
|
||||||
s->common.offset += n;
|
block_job_progress_update(&s->common, n);
|
||||||
|
|
||||||
if (copy && s->common.speed) {
|
if (copy) {
|
||||||
delay_ns = ratelimit_calculate_delay(&s->limit, n);
|
delay_ns = block_job_ratelimit_get_delay(&s->common, n);
|
||||||
} else {
|
} else {
|
||||||
delay_ns = 0;
|
delay_ns = 0;
|
||||||
}
|
}
|
||||||
@ -217,21 +214,9 @@ out:
|
|||||||
block_job_defer_to_main_loop(&s->common, commit_complete, data);
|
block_job_defer_to_main_loop(&s->common, commit_complete, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void commit_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|
||||||
{
|
|
||||||
CommitBlockJob *s = container_of(job, CommitBlockJob, common);
|
|
||||||
|
|
||||||
if (speed < 0) {
|
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const BlockJobDriver commit_job_driver = {
|
static const BlockJobDriver commit_job_driver = {
|
||||||
.instance_size = sizeof(CommitBlockJob),
|
.instance_size = sizeof(CommitBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
||||||
.set_speed = commit_set_speed,
|
|
||||||
.start = commit_run,
|
.start = commit_run,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
173
block/copy-on-read.c
Normal file
173
block/copy-on-read.c
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* Copy-on-read filter block driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* Author:
|
||||||
|
* Max Reitz <mreitz@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 or
|
||||||
|
* (at your option) version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "block/block_int.h"
|
||||||
|
|
||||||
|
|
||||||
|
static int cor_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file, false,
|
||||||
|
errp);
|
||||||
|
if (!bs->file) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||||
|
(BDRV_REQ_FUA &
|
||||||
|
bs->file->bs->supported_write_flags);
|
||||||
|
|
||||||
|
bs->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||||
|
((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP) &
|
||||||
|
bs->file->bs->supported_zero_flags);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void cor_close(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define PERM_PASSTHROUGH (BLK_PERM_CONSISTENT_READ \
|
||||||
|
| BLK_PERM_WRITE \
|
||||||
|
| BLK_PERM_RESIZE)
|
||||||
|
#define PERM_UNCHANGED (BLK_PERM_ALL & ~PERM_PASSTHROUGH)
|
||||||
|
|
||||||
|
static void cor_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||||
|
const BdrvChildRole *role,
|
||||||
|
BlockReopenQueue *reopen_queue,
|
||||||
|
uint64_t perm, uint64_t shared,
|
||||||
|
uint64_t *nperm, uint64_t *nshared)
|
||||||
|
{
|
||||||
|
if (c == NULL) {
|
||||||
|
*nperm = (perm & PERM_PASSTHROUGH) | BLK_PERM_WRITE_UNCHANGED;
|
||||||
|
*nshared = (shared & PERM_PASSTHROUGH) | PERM_UNCHANGED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*nperm = (perm & PERM_PASSTHROUGH) |
|
||||||
|
(c->perm & PERM_UNCHANGED);
|
||||||
|
*nshared = (shared & PERM_PASSTHROUGH) |
|
||||||
|
(c->shared_perm & PERM_UNCHANGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int64_t cor_getlength(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
return bdrv_getlength(bs->file->bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int cor_truncate(BlockDriverState *bs, int64_t offset,
|
||||||
|
PreallocMode prealloc, Error **errp)
|
||||||
|
{
|
||||||
|
return bdrv_truncate(bs->file, offset, prealloc, errp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int coroutine_fn cor_co_preadv(BlockDriverState *bs,
|
||||||
|
uint64_t offset, uint64_t bytes,
|
||||||
|
QEMUIOVector *qiov, int flags)
|
||||||
|
{
|
||||||
|
return bdrv_co_preadv(bs->file, offset, bytes, qiov,
|
||||||
|
flags | BDRV_REQ_COPY_ON_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int coroutine_fn cor_co_pwritev(BlockDriverState *bs,
|
||||||
|
uint64_t offset, uint64_t bytes,
|
||||||
|
QEMUIOVector *qiov, int flags)
|
||||||
|
{
|
||||||
|
|
||||||
|
return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int coroutine_fn cor_co_pwrite_zeroes(BlockDriverState *bs,
|
||||||
|
int64_t offset, int bytes,
|
||||||
|
BdrvRequestFlags flags)
|
||||||
|
{
|
||||||
|
return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int coroutine_fn cor_co_pdiscard(BlockDriverState *bs,
|
||||||
|
int64_t offset, int bytes)
|
||||||
|
{
|
||||||
|
return bdrv_co_pdiscard(bs->file->bs, offset, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void cor_eject(BlockDriverState *bs, bool eject_flag)
|
||||||
|
{
|
||||||
|
bdrv_eject(bs->file->bs, eject_flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void cor_lock_medium(BlockDriverState *bs, bool locked)
|
||||||
|
{
|
||||||
|
bdrv_lock_medium(bs->file->bs, locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool cor_recurse_is_first_non_filter(BlockDriverState *bs,
|
||||||
|
BlockDriverState *candidate)
|
||||||
|
{
|
||||||
|
return bdrv_recurse_is_first_non_filter(bs->file->bs, candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BlockDriver bdrv_copy_on_read = {
|
||||||
|
.format_name = "copy-on-read",
|
||||||
|
|
||||||
|
.bdrv_open = cor_open,
|
||||||
|
.bdrv_close = cor_close,
|
||||||
|
.bdrv_child_perm = cor_child_perm,
|
||||||
|
|
||||||
|
.bdrv_getlength = cor_getlength,
|
||||||
|
.bdrv_truncate = cor_truncate,
|
||||||
|
|
||||||
|
.bdrv_co_preadv = cor_co_preadv,
|
||||||
|
.bdrv_co_pwritev = cor_co_pwritev,
|
||||||
|
.bdrv_co_pwrite_zeroes = cor_co_pwrite_zeroes,
|
||||||
|
.bdrv_co_pdiscard = cor_co_pdiscard,
|
||||||
|
|
||||||
|
.bdrv_eject = cor_eject,
|
||||||
|
.bdrv_lock_medium = cor_lock_medium,
|
||||||
|
|
||||||
|
.bdrv_co_block_status = bdrv_co_block_status_from_file,
|
||||||
|
|
||||||
|
.bdrv_recurse_is_first_non_filter = cor_recurse_is_first_non_filter,
|
||||||
|
|
||||||
|
.has_variable_length = true,
|
||||||
|
.is_filter = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void bdrv_copy_on_read_init(void)
|
||||||
|
{
|
||||||
|
bdrv_register(&bdrv_copy_on_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
block_init(bdrv_copy_on_read_init);
|
@ -251,7 +251,11 @@ static void raw_probe_alignment(BlockDriverState *bs, Error **errp)
|
|||||||
&dg.Geometry.BytesPerSector,
|
&dg.Geometry.BytesPerSector,
|
||||||
&freeClusters, &totalClusters);
|
&freeClusters, &totalClusters);
|
||||||
bs->bl.request_alignment = dg.Geometry.BytesPerSector;
|
bs->bl.request_alignment = dg.Geometry.BytesPerSector;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* XXX Does Windows support AIO on less than 512-byte alignment? */
|
||||||
|
bs->bl.request_alignment = 512;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void raw_parse_flags(int flags, bool use_aio, int *access_flags,
|
static void raw_parse_flags(int flags, bool use_aio, int *access_flags,
|
||||||
@ -410,32 +414,32 @@ fail:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *raw_aio_readv(BlockDriverState *bs,
|
static BlockAIOCB *raw_aio_preadv(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
uint64_t offset, uint64_t bytes,
|
||||||
|
QEMUIOVector *qiov, int flags,
|
||||||
BlockCompletionFunc *cb, void *opaque)
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
BDRVRawState *s = bs->opaque;
|
BDRVRawState *s = bs->opaque;
|
||||||
if (s->aio) {
|
if (s->aio) {
|
||||||
return win32_aio_submit(bs, s->aio, s->hfile, sector_num, qiov,
|
return win32_aio_submit(bs, s->aio, s->hfile, offset, bytes, qiov,
|
||||||
nb_sectors, cb, opaque, QEMU_AIO_READ);
|
cb, opaque, QEMU_AIO_READ);
|
||||||
} else {
|
} else {
|
||||||
return paio_submit(bs, s->hfile, sector_num << BDRV_SECTOR_BITS, qiov,
|
return paio_submit(bs, s->hfile, offset, qiov, bytes,
|
||||||
nb_sectors << BDRV_SECTOR_BITS,
|
|
||||||
cb, opaque, QEMU_AIO_READ);
|
cb, opaque, QEMU_AIO_READ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *raw_aio_writev(BlockDriverState *bs,
|
static BlockAIOCB *raw_aio_pwritev(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
uint64_t offset, uint64_t bytes,
|
||||||
|
QEMUIOVector *qiov, int flags,
|
||||||
BlockCompletionFunc *cb, void *opaque)
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
BDRVRawState *s = bs->opaque;
|
BDRVRawState *s = bs->opaque;
|
||||||
if (s->aio) {
|
if (s->aio) {
|
||||||
return win32_aio_submit(bs, s->aio, s->hfile, sector_num, qiov,
|
return win32_aio_submit(bs, s->aio, s->hfile, offset, bytes, qiov,
|
||||||
nb_sectors, cb, opaque, QEMU_AIO_WRITE);
|
cb, opaque, QEMU_AIO_WRITE);
|
||||||
} else {
|
} else {
|
||||||
return paio_submit(bs, s->hfile, sector_num << BDRV_SECTOR_BITS, qiov,
|
return paio_submit(bs, s->hfile, offset, qiov, bytes,
|
||||||
nb_sectors << BDRV_SECTOR_BITS,
|
|
||||||
cb, opaque, QEMU_AIO_WRITE);
|
cb, opaque, QEMU_AIO_WRITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -632,8 +636,8 @@ BlockDriver bdrv_file = {
|
|||||||
.bdrv_co_create_opts = raw_co_create_opts,
|
.bdrv_co_create_opts = raw_co_create_opts,
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||||
|
|
||||||
.bdrv_aio_readv = raw_aio_readv,
|
.bdrv_aio_preadv = raw_aio_preadv,
|
||||||
.bdrv_aio_writev = raw_aio_writev,
|
.bdrv_aio_pwritev = raw_aio_pwritev,
|
||||||
.bdrv_aio_flush = raw_aio_flush,
|
.bdrv_aio_flush = raw_aio_flush,
|
||||||
|
|
||||||
.bdrv_truncate = raw_truncate,
|
.bdrv_truncate = raw_truncate,
|
||||||
@ -708,6 +712,12 @@ static void hdev_parse_filename(const char *filename, QDict *options,
|
|||||||
bdrv_parse_filename_strip_prefix(filename, "host_device:", options);
|
bdrv_parse_filename_strip_prefix(filename, "host_device:", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void hdev_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||||
|
{
|
||||||
|
/* XXX Does Windows support AIO on less than 512-byte alignment? */
|
||||||
|
bs->bl.request_alignment = 512;
|
||||||
|
}
|
||||||
|
|
||||||
static int hdev_open(BlockDriverState *bs, QDict *options, int flags,
|
static int hdev_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
@ -793,9 +803,10 @@ static BlockDriver bdrv_host_device = {
|
|||||||
.bdrv_probe_device = hdev_probe_device,
|
.bdrv_probe_device = hdev_probe_device,
|
||||||
.bdrv_file_open = hdev_open,
|
.bdrv_file_open = hdev_open,
|
||||||
.bdrv_close = raw_close,
|
.bdrv_close = raw_close,
|
||||||
|
.bdrv_refresh_limits = hdev_refresh_limits,
|
||||||
|
|
||||||
.bdrv_aio_readv = raw_aio_readv,
|
.bdrv_aio_preadv = raw_aio_preadv,
|
||||||
.bdrv_aio_writev = raw_aio_writev,
|
.bdrv_aio_pwritev = raw_aio_pwritev,
|
||||||
.bdrv_aio_flush = raw_aio_flush,
|
.bdrv_aio_flush = raw_aio_flush,
|
||||||
|
|
||||||
.bdrv_detach_aio_context = raw_detach_aio_context,
|
.bdrv_detach_aio_context = raw_detach_aio_context,
|
||||||
|
@ -1194,8 +1194,10 @@ static coroutine_fn int qemu_gluster_co_readv(BlockDriverState *bs,
|
|||||||
static coroutine_fn int qemu_gluster_co_writev(BlockDriverState *bs,
|
static coroutine_fn int qemu_gluster_co_writev(BlockDriverState *bs,
|
||||||
int64_t sector_num,
|
int64_t sector_num,
|
||||||
int nb_sectors,
|
int nb_sectors,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
|
assert(!flags);
|
||||||
return qemu_gluster_co_rw(bs, sector_num, nb_sectors, qiov, 1);
|
return qemu_gluster_co_rw(bs, sector_num, nb_sectors, qiov, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
77
block/io.c
77
block/io.c
@ -92,7 +92,8 @@ void bdrv_refresh_limits(BlockDriverState *bs, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Default alignment based on whether driver has byte interface */
|
/* Default alignment based on whether driver has byte interface */
|
||||||
bs->bl.request_alignment = drv->bdrv_co_preadv ? 1 : 512;
|
bs->bl.request_alignment = (drv->bdrv_co_preadv ||
|
||||||
|
drv->bdrv_aio_preadv) ? 1 : 512;
|
||||||
|
|
||||||
/* Take some limits from the children as a default */
|
/* Take some limits from the children as a default */
|
||||||
if (bs->file) {
|
if (bs->file) {
|
||||||
@ -924,22 +925,13 @@ static int coroutine_fn bdrv_driver_preadv(BlockDriverState *bs,
|
|||||||
return drv->bdrv_co_preadv(bs, offset, bytes, qiov, flags);
|
return drv->bdrv_co_preadv(bs, offset, bytes, qiov, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
sector_num = offset >> BDRV_SECTOR_BITS;
|
if (drv->bdrv_aio_preadv) {
|
||||||
nb_sectors = bytes >> BDRV_SECTOR_BITS;
|
|
||||||
|
|
||||||
assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
assert((bytes >> BDRV_SECTOR_BITS) <= BDRV_REQUEST_MAX_SECTORS);
|
|
||||||
|
|
||||||
if (drv->bdrv_co_readv) {
|
|
||||||
return drv->bdrv_co_readv(bs, sector_num, nb_sectors, qiov);
|
|
||||||
} else {
|
|
||||||
BlockAIOCB *acb;
|
BlockAIOCB *acb;
|
||||||
CoroutineIOCompletion co = {
|
CoroutineIOCompletion co = {
|
||||||
.coroutine = qemu_coroutine_self(),
|
.coroutine = qemu_coroutine_self(),
|
||||||
};
|
};
|
||||||
|
|
||||||
acb = bs->drv->bdrv_aio_readv(bs, sector_num, qiov, nb_sectors,
|
acb = drv->bdrv_aio_preadv(bs, offset, bytes, qiov, flags,
|
||||||
bdrv_co_io_em_complete, &co);
|
bdrv_co_io_em_complete, &co);
|
||||||
if (acb == NULL) {
|
if (acb == NULL) {
|
||||||
return -EIO;
|
return -EIO;
|
||||||
@ -948,6 +940,16 @@ static int coroutine_fn bdrv_driver_preadv(BlockDriverState *bs,
|
|||||||
return co.ret;
|
return co.ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sector_num = offset >> BDRV_SECTOR_BITS;
|
||||||
|
nb_sectors = bytes >> BDRV_SECTOR_BITS;
|
||||||
|
|
||||||
|
assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||||
|
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||||
|
assert((bytes >> BDRV_SECTOR_BITS) <= BDRV_REQUEST_MAX_SECTORS);
|
||||||
|
assert(drv->bdrv_co_readv);
|
||||||
|
|
||||||
|
return drv->bdrv_co_readv(bs, sector_num, nb_sectors, qiov);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn bdrv_driver_pwritev(BlockDriverState *bs,
|
static int coroutine_fn bdrv_driver_pwritev(BlockDriverState *bs,
|
||||||
@ -972,6 +974,25 @@ static int coroutine_fn bdrv_driver_pwritev(BlockDriverState *bs,
|
|||||||
goto emulate_flags;
|
goto emulate_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (drv->bdrv_aio_pwritev) {
|
||||||
|
BlockAIOCB *acb;
|
||||||
|
CoroutineIOCompletion co = {
|
||||||
|
.coroutine = qemu_coroutine_self(),
|
||||||
|
};
|
||||||
|
|
||||||
|
acb = drv->bdrv_aio_pwritev(bs, offset, bytes, qiov,
|
||||||
|
flags & bs->supported_write_flags,
|
||||||
|
bdrv_co_io_em_complete, &co);
|
||||||
|
flags &= ~bs->supported_write_flags;
|
||||||
|
if (acb == NULL) {
|
||||||
|
ret = -EIO;
|
||||||
|
} else {
|
||||||
|
qemu_coroutine_yield();
|
||||||
|
ret = co.ret;
|
||||||
|
}
|
||||||
|
goto emulate_flags;
|
||||||
|
}
|
||||||
|
|
||||||
sector_num = offset >> BDRV_SECTOR_BITS;
|
sector_num = offset >> BDRV_SECTOR_BITS;
|
||||||
nb_sectors = bytes >> BDRV_SECTOR_BITS;
|
nb_sectors = bytes >> BDRV_SECTOR_BITS;
|
||||||
|
|
||||||
@ -979,28 +1000,10 @@ static int coroutine_fn bdrv_driver_pwritev(BlockDriverState *bs,
|
|||||||
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||||
assert((bytes >> BDRV_SECTOR_BITS) <= BDRV_REQUEST_MAX_SECTORS);
|
assert((bytes >> BDRV_SECTOR_BITS) <= BDRV_REQUEST_MAX_SECTORS);
|
||||||
|
|
||||||
if (drv->bdrv_co_writev_flags) {
|
assert(drv->bdrv_co_writev);
|
||||||
ret = drv->bdrv_co_writev_flags(bs, sector_num, nb_sectors, qiov,
|
ret = drv->bdrv_co_writev(bs, sector_num, nb_sectors, qiov,
|
||||||
flags & bs->supported_write_flags);
|
flags & bs->supported_write_flags);
|
||||||
flags &= ~bs->supported_write_flags;
|
flags &= ~bs->supported_write_flags;
|
||||||
} else if (drv->bdrv_co_writev) {
|
|
||||||
assert(!bs->supported_write_flags);
|
|
||||||
ret = drv->bdrv_co_writev(bs, sector_num, nb_sectors, qiov);
|
|
||||||
} else {
|
|
||||||
BlockAIOCB *acb;
|
|
||||||
CoroutineIOCompletion co = {
|
|
||||||
.coroutine = qemu_coroutine_self(),
|
|
||||||
};
|
|
||||||
|
|
||||||
acb = bs->drv->bdrv_aio_writev(bs, sector_num, qiov, nb_sectors,
|
|
||||||
bdrv_co_io_em_complete, &co);
|
|
||||||
if (acb == NULL) {
|
|
||||||
ret = -EIO;
|
|
||||||
} else {
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
ret = co.ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emulate_flags:
|
emulate_flags:
|
||||||
if (ret == 0 && (flags & BDRV_REQ_FUA)) {
|
if (ret == 0 && (flags & BDRV_REQ_FUA)) {
|
||||||
@ -1115,13 +1118,15 @@ static int coroutine_fn bdrv_co_do_copy_on_readv(BdrvChild *child,
|
|||||||
/* FIXME: Should we (perhaps conditionally) be setting
|
/* FIXME: Should we (perhaps conditionally) be setting
|
||||||
* BDRV_REQ_MAY_UNMAP, if it will allow for a sparser copy
|
* BDRV_REQ_MAY_UNMAP, if it will allow for a sparser copy
|
||||||
* that still correctly reads as zero? */
|
* that still correctly reads as zero? */
|
||||||
ret = bdrv_co_do_pwrite_zeroes(bs, cluster_offset, pnum, 0);
|
ret = bdrv_co_do_pwrite_zeroes(bs, cluster_offset, pnum,
|
||||||
|
BDRV_REQ_WRITE_UNCHANGED);
|
||||||
} else {
|
} else {
|
||||||
/* This does not change the data on the disk, it is not
|
/* This does not change the data on the disk, it is not
|
||||||
* necessary to flush even in cache=writethrough mode.
|
* necessary to flush even in cache=writethrough mode.
|
||||||
*/
|
*/
|
||||||
ret = bdrv_driver_pwritev(bs, cluster_offset, pnum,
|
ret = bdrv_driver_pwritev(bs, cluster_offset, pnum,
|
||||||
&local_qiov, 0);
|
&local_qiov,
|
||||||
|
BDRV_REQ_WRITE_UNCHANGED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@ -1501,7 +1506,11 @@ static int coroutine_fn bdrv_aligned_pwritev(BdrvChild *child,
|
|||||||
assert(!waited || !req->serialising);
|
assert(!waited || !req->serialising);
|
||||||
assert(req->overlap_offset <= offset);
|
assert(req->overlap_offset <= offset);
|
||||||
assert(offset + bytes <= req->overlap_offset + req->overlap_bytes);
|
assert(offset + bytes <= req->overlap_offset + req->overlap_bytes);
|
||||||
|
if (flags & BDRV_REQ_WRITE_UNCHANGED) {
|
||||||
|
assert(child->perm & (BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE));
|
||||||
|
} else {
|
||||||
assert(child->perm & BLK_PERM_WRITE);
|
assert(child->perm & BLK_PERM_WRITE);
|
||||||
|
}
|
||||||
assert(end_sector <= bs->total_sectors || child->perm & BLK_PERM_RESIZE);
|
assert(end_sector <= bs->total_sectors || child->perm & BLK_PERM_RESIZE);
|
||||||
|
|
||||||
ret = notifier_with_return_list_notify(&bs->before_write_notifiers, req);
|
ret = notifier_with_return_list_notify(&bs->before_write_notifiers, req);
|
||||||
|
@ -556,7 +556,7 @@ static inline bool iscsi_allocmap_is_valid(IscsiLun *iscsilun,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn
|
static int coroutine_fn
|
||||||
iscsi_co_writev_flags(BlockDriverState *bs, int64_t sector_num, int nb_sectors,
|
iscsi_co_writev(BlockDriverState *bs, int64_t sector_num, int nb_sectors,
|
||||||
QEMUIOVector *iov, int flags)
|
QEMUIOVector *iov, int flags)
|
||||||
{
|
{
|
||||||
IscsiLun *iscsilun = bs->opaque;
|
IscsiLun *iscsilun = bs->opaque;
|
||||||
@ -2220,7 +2220,7 @@ static BlockDriver bdrv_iscsi = {
|
|||||||
.bdrv_co_pdiscard = iscsi_co_pdiscard,
|
.bdrv_co_pdiscard = iscsi_co_pdiscard,
|
||||||
.bdrv_co_pwrite_zeroes = iscsi_co_pwrite_zeroes,
|
.bdrv_co_pwrite_zeroes = iscsi_co_pwrite_zeroes,
|
||||||
.bdrv_co_readv = iscsi_co_readv,
|
.bdrv_co_readv = iscsi_co_readv,
|
||||||
.bdrv_co_writev_flags = iscsi_co_writev_flags,
|
.bdrv_co_writev = iscsi_co_writev,
|
||||||
.bdrv_co_flush_to_disk = iscsi_co_flush,
|
.bdrv_co_flush_to_disk = iscsi_co_flush,
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
@ -2255,7 +2255,7 @@ static BlockDriver bdrv_iser = {
|
|||||||
.bdrv_co_pdiscard = iscsi_co_pdiscard,
|
.bdrv_co_pdiscard = iscsi_co_pdiscard,
|
||||||
.bdrv_co_pwrite_zeroes = iscsi_co_pwrite_zeroes,
|
.bdrv_co_pwrite_zeroes = iscsi_co_pwrite_zeroes,
|
||||||
.bdrv_co_readv = iscsi_co_readv,
|
.bdrv_co_readv = iscsi_co_readv,
|
||||||
.bdrv_co_writev_flags = iscsi_co_writev_flags,
|
.bdrv_co_writev = iscsi_co_writev,
|
||||||
.bdrv_co_flush_to_disk = iscsi_co_flush,
|
.bdrv_co_flush_to_disk = iscsi_co_flush,
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
#include "qemu/ratelimit.h"
|
#include "qemu/ratelimit.h"
|
||||||
#include "qemu/bitmap.h"
|
#include "qemu/bitmap.h"
|
||||||
|
|
||||||
#define SLICE_TIME 100000000ULL /* ns */
|
|
||||||
#define MAX_IN_FLIGHT 16
|
#define MAX_IN_FLIGHT 16
|
||||||
#define MAX_IO_BYTES (1 << 20) /* 1 Mb */
|
#define MAX_IO_BYTES (1 << 20) /* 1 Mb */
|
||||||
#define DEFAULT_MIRROR_BUF_SIZE (MAX_IN_FLIGHT * MAX_IO_BYTES)
|
#define DEFAULT_MIRROR_BUF_SIZE (MAX_IN_FLIGHT * MAX_IO_BYTES)
|
||||||
@ -36,7 +35,6 @@ typedef struct MirrorBuffer {
|
|||||||
|
|
||||||
typedef struct MirrorBlockJob {
|
typedef struct MirrorBlockJob {
|
||||||
BlockJob common;
|
BlockJob common;
|
||||||
RateLimit limit;
|
|
||||||
BlockBackend *target;
|
BlockBackend *target;
|
||||||
BlockDriverState *mirror_top_bs;
|
BlockDriverState *mirror_top_bs;
|
||||||
BlockDriverState *source;
|
BlockDriverState *source;
|
||||||
@ -121,7 +119,7 @@ static void mirror_iteration_done(MirrorOp *op, int ret)
|
|||||||
bitmap_set(s->cow_bitmap, chunk_num, nb_chunks);
|
bitmap_set(s->cow_bitmap, chunk_num, nb_chunks);
|
||||||
}
|
}
|
||||||
if (!s->initial_zeroing_ongoing) {
|
if (!s->initial_zeroing_ongoing) {
|
||||||
s->common.offset += op->bytes;
|
block_job_progress_update(&s->common, op->bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qemu_iovec_destroy(&op->qiov);
|
qemu_iovec_destroy(&op->qiov);
|
||||||
@ -449,9 +447,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
|||||||
assert(io_bytes);
|
assert(io_bytes);
|
||||||
offset += io_bytes;
|
offset += io_bytes;
|
||||||
nb_chunks -= DIV_ROUND_UP(io_bytes, s->granularity);
|
nb_chunks -= DIV_ROUND_UP(io_bytes, s->granularity);
|
||||||
if (s->common.speed) {
|
delay_ns = block_job_ratelimit_get_delay(&s->common, io_bytes_acct);
|
||||||
delay_ns = ratelimit_calculate_delay(&s->limit, io_bytes_acct);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return delay_ns;
|
return delay_ns;
|
||||||
}
|
}
|
||||||
@ -596,7 +592,7 @@ static void mirror_throttle(MirrorBlockJob *s)
|
|||||||
{
|
{
|
||||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
||||||
|
|
||||||
if (now - s->last_pause_ns > SLICE_TIME) {
|
if (now - s->last_pause_ns > BLOCK_JOB_SLICE_TIME) {
|
||||||
s->last_pause_ns = now;
|
s->last_pause_ns = now;
|
||||||
block_job_sleep_ns(&s->common, 0);
|
block_job_sleep_ns(&s->common, 0);
|
||||||
} else {
|
} else {
|
||||||
@ -792,19 +788,17 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
block_job_pause_point(&s->common);
|
block_job_pause_point(&s->common);
|
||||||
|
|
||||||
cnt = bdrv_get_dirty_count(s->dirty_bitmap);
|
cnt = bdrv_get_dirty_count(s->dirty_bitmap);
|
||||||
/* s->common.offset contains the number of bytes already processed so
|
/* cnt is the number of dirty bytes remaining and s->bytes_in_flight is
|
||||||
* far, cnt is the number of dirty bytes remaining and
|
* the number of bytes currently being processed; together those are
|
||||||
* s->bytes_in_flight is the number of bytes currently being
|
* the current remaining operation length */
|
||||||
* processed; together those are the current total operation length */
|
block_job_progress_set_remaining(&s->common, s->bytes_in_flight + cnt);
|
||||||
s->common.len = s->common.offset + s->bytes_in_flight + cnt;
|
|
||||||
|
|
||||||
/* 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
|
||||||
* periodically with no pending I/O so that bdrv_drain_all() returns.
|
* periodically with no pending I/O so that bdrv_drain_all() returns.
|
||||||
* We do so every SLICE_TIME nanoseconds, or when there is an error,
|
* We do so every BLKOCK_JOB_SLICE_TIME nanoseconds, or when there is
|
||||||
* or when the source is clean, whichever comes first.
|
* an error, or when the source is clean, whichever comes first. */
|
||||||
*/
|
|
||||||
delta = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - s->last_pause_ns;
|
delta = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - s->last_pause_ns;
|
||||||
if (delta < SLICE_TIME &&
|
if (delta < BLOCK_JOB_SLICE_TIME &&
|
||||||
s->common.iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
|
s->common.iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
|
||||||
if (s->in_flight >= MAX_IN_FLIGHT || s->buf_free_count == 0 ||
|
if (s->in_flight >= MAX_IN_FLIGHT || s->buf_free_count == 0 ||
|
||||||
(cnt == 0 && s->in_flight > 0)) {
|
(cnt == 0 && s->in_flight > 0)) {
|
||||||
@ -870,7 +864,8 @@ static void coroutine_fn mirror_run(void *opaque)
|
|||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
||||||
if (s->synced && !should_complete) {
|
if (s->synced && !should_complete) {
|
||||||
delay_ns = (s->in_flight == 0 && cnt == 0 ? SLICE_TIME : 0);
|
delay_ns = (s->in_flight == 0 &&
|
||||||
|
cnt == 0 ? BLOCK_JOB_SLICE_TIME : 0);
|
||||||
}
|
}
|
||||||
trace_mirror_before_sleep(s, cnt, s->synced, delay_ns);
|
trace_mirror_before_sleep(s, cnt, s->synced, delay_ns);
|
||||||
block_job_sleep_ns(&s->common, delay_ns);
|
block_job_sleep_ns(&s->common, delay_ns);
|
||||||
@ -909,17 +904,6 @@ immediate_exit:
|
|||||||
block_job_defer_to_main_loop(&s->common, mirror_exit, data);
|
block_job_defer_to_main_loop(&s->common, mirror_exit, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|
||||||
{
|
|
||||||
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
|
||||||
|
|
||||||
if (speed < 0) {
|
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mirror_complete(BlockJob *job, Error **errp)
|
static void mirror_complete(BlockJob *job, Error **errp)
|
||||||
{
|
{
|
||||||
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
||||||
@ -1004,7 +988,6 @@ static void mirror_drain(BlockJob *job)
|
|||||||
static const BlockJobDriver mirror_job_driver = {
|
static const BlockJobDriver mirror_job_driver = {
|
||||||
.instance_size = sizeof(MirrorBlockJob),
|
.instance_size = sizeof(MirrorBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_MIRROR,
|
.job_type = BLOCK_JOB_TYPE_MIRROR,
|
||||||
.set_speed = mirror_set_speed,
|
|
||||||
.start = mirror_run,
|
.start = mirror_run,
|
||||||
.complete = mirror_complete,
|
.complete = mirror_complete,
|
||||||
.pause = mirror_pause,
|
.pause = mirror_pause,
|
||||||
@ -1015,7 +998,6 @@ static const BlockJobDriver mirror_job_driver = {
|
|||||||
static const BlockJobDriver commit_active_job_driver = {
|
static const BlockJobDriver commit_active_job_driver = {
|
||||||
.instance_size = sizeof(MirrorBlockJob),
|
.instance_size = sizeof(MirrorBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
||||||
.set_speed = mirror_set_speed,
|
|
||||||
.start = mirror_run,
|
.start = mirror_run,
|
||||||
.complete = mirror_complete,
|
.complete = mirror_complete,
|
||||||
.pause = mirror_pause,
|
.pause = mirror_pause,
|
||||||
@ -1152,6 +1134,8 @@ static void mirror_start_job(const char *job_id, BlockDriverState *bs,
|
|||||||
mirror_top_bs->implicit = true;
|
mirror_top_bs->implicit = true;
|
||||||
}
|
}
|
||||||
mirror_top_bs->total_sectors = bs->total_sectors;
|
mirror_top_bs->total_sectors = bs->total_sectors;
|
||||||
|
mirror_top_bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED;
|
||||||
|
mirror_top_bs->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED;
|
||||||
bdrv_set_aio_context(mirror_top_bs, bdrv_get_aio_context(bs));
|
bdrv_set_aio_context(mirror_top_bs, bdrv_get_aio_context(bs));
|
||||||
|
|
||||||
/* bdrv_append takes ownership of the mirror_top_bs reference, need to keep
|
/* bdrv_append takes ownership of the mirror_top_bs reference, need to keep
|
||||||
|
37
block/null.c
37
block/null.c
@ -93,6 +93,7 @@ static int null_file_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
s->read_zeroes = qemu_opt_get_bool(opts, NULL_OPT_ZEROES, false);
|
s->read_zeroes = qemu_opt_get_bool(opts, NULL_OPT_ZEROES, false);
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
|
bs->supported_write_flags = BDRV_REQ_FUA;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,22 +117,22 @@ static coroutine_fn int null_co_common(BlockDriverState *bs)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int null_co_readv(BlockDriverState *bs,
|
static coroutine_fn int null_co_preadv(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors,
|
uint64_t offset, uint64_t bytes,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov, int flags)
|
||||||
{
|
{
|
||||||
BDRVNullState *s = bs->opaque;
|
BDRVNullState *s = bs->opaque;
|
||||||
|
|
||||||
if (s->read_zeroes) {
|
if (s->read_zeroes) {
|
||||||
qemu_iovec_memset(qiov, 0, 0, nb_sectors * BDRV_SECTOR_SIZE);
|
qemu_iovec_memset(qiov, 0, 0, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null_co_common(bs);
|
return null_co_common(bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int null_co_writev(BlockDriverState *bs,
|
static coroutine_fn int null_co_pwritev(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors,
|
uint64_t offset, uint64_t bytes,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov, int flags)
|
||||||
{
|
{
|
||||||
return null_co_common(bs);
|
return null_co_common(bs);
|
||||||
}
|
}
|
||||||
@ -186,24 +187,24 @@ static inline BlockAIOCB *null_aio_common(BlockDriverState *bs,
|
|||||||
return &acb->common;
|
return &acb->common;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *null_aio_readv(BlockDriverState *bs,
|
static BlockAIOCB *null_aio_preadv(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov,
|
uint64_t offset, uint64_t bytes,
|
||||||
int nb_sectors,
|
QEMUIOVector *qiov, int flags,
|
||||||
BlockCompletionFunc *cb,
|
BlockCompletionFunc *cb,
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
BDRVNullState *s = bs->opaque;
|
BDRVNullState *s = bs->opaque;
|
||||||
|
|
||||||
if (s->read_zeroes) {
|
if (s->read_zeroes) {
|
||||||
qemu_iovec_memset(qiov, 0, 0, nb_sectors * BDRV_SECTOR_SIZE);
|
qemu_iovec_memset(qiov, 0, 0, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null_aio_common(bs, cb, opaque);
|
return null_aio_common(bs, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *null_aio_writev(BlockDriverState *bs,
|
static BlockAIOCB *null_aio_pwritev(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov,
|
uint64_t offset, uint64_t bytes,
|
||||||
int nb_sectors,
|
QEMUIOVector *qiov, int flags,
|
||||||
BlockCompletionFunc *cb,
|
BlockCompletionFunc *cb,
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
@ -265,8 +266,8 @@ static BlockDriver bdrv_null_co = {
|
|||||||
.bdrv_close = null_close,
|
.bdrv_close = null_close,
|
||||||
.bdrv_getlength = null_getlength,
|
.bdrv_getlength = null_getlength,
|
||||||
|
|
||||||
.bdrv_co_readv = null_co_readv,
|
.bdrv_co_preadv = null_co_preadv,
|
||||||
.bdrv_co_writev = null_co_writev,
|
.bdrv_co_pwritev = null_co_pwritev,
|
||||||
.bdrv_co_flush_to_disk = null_co_flush,
|
.bdrv_co_flush_to_disk = null_co_flush,
|
||||||
.bdrv_reopen_prepare = null_reopen_prepare,
|
.bdrv_reopen_prepare = null_reopen_prepare,
|
||||||
|
|
||||||
@ -285,8 +286,8 @@ static BlockDriver bdrv_null_aio = {
|
|||||||
.bdrv_close = null_close,
|
.bdrv_close = null_close,
|
||||||
.bdrv_getlength = null_getlength,
|
.bdrv_getlength = null_getlength,
|
||||||
|
|
||||||
.bdrv_aio_readv = null_aio_readv,
|
.bdrv_aio_preadv = null_aio_preadv,
|
||||||
.bdrv_aio_writev = null_aio_writev,
|
.bdrv_aio_pwritev = null_aio_pwritev,
|
||||||
.bdrv_aio_flush = null_aio_flush,
|
.bdrv_aio_flush = null_aio_flush,
|
||||||
.bdrv_reopen_prepare = null_reopen_prepare,
|
.bdrv_reopen_prepare = null_reopen_prepare,
|
||||||
|
|
||||||
|
@ -311,13 +311,15 @@ static int coroutine_fn parallels_co_block_status(BlockDriverState *bs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int parallels_co_writev(BlockDriverState *bs,
|
static coroutine_fn int parallels_co_writev(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov)
|
int64_t sector_num, int nb_sectors,
|
||||||
|
QEMUIOVector *qiov, int flags)
|
||||||
{
|
{
|
||||||
BDRVParallelsState *s = bs->opaque;
|
BDRVParallelsState *s = bs->opaque;
|
||||||
uint64_t bytes_done = 0;
|
uint64_t bytes_done = 0;
|
||||||
QEMUIOVector hd_qiov;
|
QEMUIOVector hd_qiov;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
assert(!flags);
|
||||||
qemu_iovec_init(&hd_qiov, qiov->niov);
|
qemu_iovec_init(&hd_qiov, qiov->niov);
|
||||||
|
|
||||||
while (nb_sectors > 0) {
|
while (nb_sectors > 0) {
|
||||||
|
@ -720,7 +720,8 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
|
static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
int nb_sectors, QEMUIOVector *qiov,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
int index_in_cluster;
|
int index_in_cluster;
|
||||||
@ -731,6 +732,7 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
|
|||||||
uint8_t *buf;
|
uint8_t *buf;
|
||||||
void *orig_buf;
|
void *orig_buf;
|
||||||
|
|
||||||
|
assert(!flags);
|
||||||
s->cluster_cache_offset = -1; /* disable compressed cache */
|
s->cluster_cache_offset = -1; /* disable compressed cache */
|
||||||
|
|
||||||
/* We must always copy the iov when encrypting, so we
|
/* We must always copy the iov when encrypting, so we
|
||||||
@ -1110,7 +1112,7 @@ qcow_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
|
|||||||
if (ret != Z_STREAM_END || out_len >= s->cluster_size) {
|
if (ret != Z_STREAM_END || out_len >= s->cluster_size) {
|
||||||
/* could not compress: write normal cluster */
|
/* could not compress: write normal cluster */
|
||||||
ret = qcow_co_writev(bs, offset >> BDRV_SECTOR_BITS,
|
ret = qcow_co_writev(bs, offset >> BDRV_SECTOR_BITS,
|
||||||
bytes >> BDRV_SECTOR_BITS, qiov);
|
bytes >> BDRV_SECTOR_BITS, qiov, 0);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -1577,9 +1577,9 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
|||||||
case QCOW2_CLUSTER_COMPRESSED:
|
case QCOW2_CLUSTER_COMPRESSED:
|
||||||
/* Compressed clusters don't have QCOW_OFLAG_COPIED */
|
/* Compressed clusters don't have QCOW_OFLAG_COPIED */
|
||||||
if (l2_entry & QCOW_OFLAG_COPIED) {
|
if (l2_entry & QCOW_OFLAG_COPIED) {
|
||||||
fprintf(stderr, "ERROR: cluster %" PRId64 ": "
|
fprintf(stderr, "ERROR: coffset=0x%" PRIx64 ": "
|
||||||
"copied flag must never be set for compressed "
|
"copied flag must never be set for compressed "
|
||||||
"clusters\n", l2_entry >> s->cluster_bits);
|
"clusters\n", l2_entry & s->cluster_offset_mask);
|
||||||
l2_entry &= ~QCOW_OFLAG_COPIED;
|
l2_entry &= ~QCOW_OFLAG_COPIED;
|
||||||
res->corruptions++;
|
res->corruptions++;
|
||||||
}
|
}
|
||||||
|
@ -802,23 +802,30 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
|||||||
} else if (refcount_cache_size_set) {
|
} else if (refcount_cache_size_set) {
|
||||||
*l2_cache_size = combined_cache_size - *refcount_cache_size;
|
*l2_cache_size = combined_cache_size - *refcount_cache_size;
|
||||||
} else {
|
} else {
|
||||||
*refcount_cache_size = combined_cache_size
|
uint64_t virtual_disk_size = bs->total_sectors * BDRV_SECTOR_SIZE;
|
||||||
/ (DEFAULT_L2_REFCOUNT_SIZE_RATIO + 1);
|
uint64_t max_l2_cache = virtual_disk_size / (s->cluster_size / 8);
|
||||||
|
uint64_t min_refcount_cache =
|
||||||
|
(uint64_t) MIN_REFCOUNT_CACHE_SIZE * s->cluster_size;
|
||||||
|
|
||||||
|
/* Assign as much memory as possible to the L2 cache, and
|
||||||
|
* use the remainder for the refcount cache */
|
||||||
|
if (combined_cache_size >= max_l2_cache + min_refcount_cache) {
|
||||||
|
*l2_cache_size = max_l2_cache;
|
||||||
|
*refcount_cache_size = combined_cache_size - *l2_cache_size;
|
||||||
|
} else {
|
||||||
|
*refcount_cache_size =
|
||||||
|
MIN(combined_cache_size, min_refcount_cache);
|
||||||
*l2_cache_size = combined_cache_size - *refcount_cache_size;
|
*l2_cache_size = combined_cache_size - *refcount_cache_size;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!l2_cache_size_set && !refcount_cache_size_set) {
|
if (!l2_cache_size_set) {
|
||||||
*l2_cache_size = MAX(DEFAULT_L2_CACHE_BYTE_SIZE,
|
*l2_cache_size = MAX(DEFAULT_L2_CACHE_BYTE_SIZE,
|
||||||
(uint64_t)DEFAULT_L2_CACHE_CLUSTERS
|
(uint64_t)DEFAULT_L2_CACHE_CLUSTERS
|
||||||
* s->cluster_size);
|
* s->cluster_size);
|
||||||
*refcount_cache_size = *l2_cache_size
|
}
|
||||||
/ DEFAULT_L2_REFCOUNT_SIZE_RATIO;
|
if (!refcount_cache_size_set) {
|
||||||
} else if (!l2_cache_size_set) {
|
*refcount_cache_size = MIN_REFCOUNT_CACHE_SIZE * s->cluster_size;
|
||||||
*l2_cache_size = *refcount_cache_size
|
|
||||||
* DEFAULT_L2_REFCOUNT_SIZE_RATIO;
|
|
||||||
} else if (!refcount_cache_size_set) {
|
|
||||||
*refcount_cache_size = *l2_cache_size
|
|
||||||
/ DEFAULT_L2_REFCOUNT_SIZE_RATIO;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,10 +77,6 @@
|
|||||||
#define DEFAULT_L2_CACHE_CLUSTERS 8 /* clusters */
|
#define DEFAULT_L2_CACHE_CLUSTERS 8 /* clusters */
|
||||||
#define DEFAULT_L2_CACHE_BYTE_SIZE 1048576 /* bytes */
|
#define DEFAULT_L2_CACHE_BYTE_SIZE 1048576 /* bytes */
|
||||||
|
|
||||||
/* The refblock cache needs only a fourth of the L2 cache size to cover as many
|
|
||||||
* clusters */
|
|
||||||
#define DEFAULT_L2_REFCOUNT_SIZE_RATIO 4
|
|
||||||
|
|
||||||
#define DEFAULT_CLUSTER_SIZE 65536
|
#define DEFAULT_CLUSTER_SIZE 65536
|
||||||
|
|
||||||
|
|
||||||
|
@ -1437,8 +1437,9 @@ static int coroutine_fn bdrv_qed_co_readv(BlockDriverState *bs,
|
|||||||
|
|
||||||
static int coroutine_fn bdrv_qed_co_writev(BlockDriverState *bs,
|
static int coroutine_fn bdrv_qed_co_writev(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors,
|
int64_t sector_num, int nb_sectors,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov, int flags)
|
||||||
{
|
{
|
||||||
|
assert(!flags);
|
||||||
return qed_co_request(bs, sector_num, qiov, nb_sectors, QED_AIOCB_WRITE);
|
return qed_co_request(bs, sector_num, qiov, nb_sectors, QED_AIOCB_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +115,7 @@ struct QuorumAIOCB {
|
|||||||
/* Request metadata */
|
/* Request metadata */
|
||||||
uint64_t offset;
|
uint64_t offset;
|
||||||
uint64_t bytes;
|
uint64_t bytes;
|
||||||
|
int flags;
|
||||||
|
|
||||||
QEMUIOVector *qiov; /* calling IOV */
|
QEMUIOVector *qiov; /* calling IOV */
|
||||||
|
|
||||||
@ -157,7 +158,8 @@ static bool quorum_64bits_compare(QuorumVoteValue *a, QuorumVoteValue *b)
|
|||||||
static QuorumAIOCB *quorum_aio_get(BlockDriverState *bs,
|
static QuorumAIOCB *quorum_aio_get(BlockDriverState *bs,
|
||||||
QEMUIOVector *qiov,
|
QEMUIOVector *qiov,
|
||||||
uint64_t offset,
|
uint64_t offset,
|
||||||
uint64_t bytes)
|
uint64_t bytes,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
BDRVQuorumState *s = bs->opaque;
|
BDRVQuorumState *s = bs->opaque;
|
||||||
QuorumAIOCB *acb = g_new(QuorumAIOCB, 1);
|
QuorumAIOCB *acb = g_new(QuorumAIOCB, 1);
|
||||||
@ -168,6 +170,7 @@ static QuorumAIOCB *quorum_aio_get(BlockDriverState *bs,
|
|||||||
.bs = bs,
|
.bs = bs,
|
||||||
.offset = offset,
|
.offset = offset,
|
||||||
.bytes = bytes,
|
.bytes = bytes,
|
||||||
|
.flags = flags,
|
||||||
.qiov = qiov,
|
.qiov = qiov,
|
||||||
.votes.compare = quorum_sha256_compare,
|
.votes.compare = quorum_sha256_compare,
|
||||||
.votes.vote_list = QLIST_HEAD_INITIALIZER(acb.votes.vote_list),
|
.votes.vote_list = QLIST_HEAD_INITIALIZER(acb.votes.vote_list),
|
||||||
@ -271,9 +274,11 @@ static void quorum_rewrite_entry(void *opaque)
|
|||||||
BDRVQuorumState *s = acb->bs->opaque;
|
BDRVQuorumState *s = acb->bs->opaque;
|
||||||
|
|
||||||
/* Ignore any errors, it's just a correction attempt for already
|
/* Ignore any errors, it's just a correction attempt for already
|
||||||
* corrupted data. */
|
* corrupted data.
|
||||||
|
* Mask out BDRV_REQ_WRITE_UNCHANGED because this overwrites the
|
||||||
|
* area with different data from the other children. */
|
||||||
bdrv_co_pwritev(s->children[co->idx], acb->offset, acb->bytes,
|
bdrv_co_pwritev(s->children[co->idx], acb->offset, acb->bytes,
|
||||||
acb->qiov, 0);
|
acb->qiov, acb->flags & ~BDRV_REQ_WRITE_UNCHANGED);
|
||||||
|
|
||||||
/* Wake up the caller after the last rewrite */
|
/* Wake up the caller after the last rewrite */
|
||||||
acb->rewrite_count--;
|
acb->rewrite_count--;
|
||||||
@ -673,7 +678,7 @@ static int quorum_co_preadv(BlockDriverState *bs, uint64_t offset,
|
|||||||
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||||
{
|
{
|
||||||
BDRVQuorumState *s = bs->opaque;
|
BDRVQuorumState *s = bs->opaque;
|
||||||
QuorumAIOCB *acb = quorum_aio_get(bs, qiov, offset, bytes);
|
QuorumAIOCB *acb = quorum_aio_get(bs, qiov, offset, bytes, flags);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
acb->is_read = true;
|
acb->is_read = true;
|
||||||
@ -699,7 +704,7 @@ static void write_quorum_entry(void *opaque)
|
|||||||
|
|
||||||
sacb->bs = s->children[i]->bs;
|
sacb->bs = s->children[i]->bs;
|
||||||
sacb->ret = bdrv_co_pwritev(s->children[i], acb->offset, acb->bytes,
|
sacb->ret = bdrv_co_pwritev(s->children[i], acb->offset, acb->bytes,
|
||||||
acb->qiov, 0);
|
acb->qiov, acb->flags);
|
||||||
if (sacb->ret == 0) {
|
if (sacb->ret == 0) {
|
||||||
acb->success_count++;
|
acb->success_count++;
|
||||||
} else {
|
} else {
|
||||||
@ -719,7 +724,7 @@ static int quorum_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
|||||||
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
||||||
{
|
{
|
||||||
BDRVQuorumState *s = bs->opaque;
|
BDRVQuorumState *s = bs->opaque;
|
||||||
QuorumAIOCB *acb = quorum_aio_get(bs, qiov, offset, bytes);
|
QuorumAIOCB *acb = quorum_aio_get(bs, qiov, offset, bytes, flags);
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
|
||||||
for (i = 0; i < s->num_children; i++) {
|
for (i = 0; i < s->num_children; i++) {
|
||||||
@ -961,6 +966,8 @@ static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
s->next_child_index = s->num_children;
|
s->next_child_index = s->num_children;
|
||||||
|
|
||||||
|
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED;
|
||||||
|
|
||||||
g_free(opened);
|
g_free(opened);
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
|
@ -415,10 +415,11 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bs->sg = bs->file->bs->sg;
|
bs->sg = bs->file->bs->sg;
|
||||||
bs->supported_write_flags = BDRV_REQ_FUA &
|
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||||
bs->file->bs->supported_write_flags;
|
(BDRV_REQ_FUA & bs->file->bs->supported_write_flags);
|
||||||
bs->supported_zero_flags = (BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP) &
|
bs->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||||
bs->file->bs->supported_zero_flags;
|
((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP) &
|
||||||
|
bs->file->bs->supported_zero_flags);
|
||||||
|
|
||||||
if (bs->probed && !bdrv_is_read_only(bs)) {
|
if (bs->probed && !bdrv_is_read_only(bs)) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
|
32
block/rbd.c
32
block/rbd.c
@ -231,6 +231,13 @@ done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void qemu_rbd_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||||
|
{
|
||||||
|
/* XXX Does RBD support AIO on less than 512-byte alignment? */
|
||||||
|
bs->bl.request_alignment = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int qemu_rbd_set_auth(rados_t cluster, const char *secretid,
|
static int qemu_rbd_set_auth(rados_t cluster, const char *secretid,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
@ -899,27 +906,23 @@ failed:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *qemu_rbd_aio_readv(BlockDriverState *bs,
|
static BlockAIOCB *qemu_rbd_aio_preadv(BlockDriverState *bs,
|
||||||
int64_t sector_num,
|
uint64_t offset, uint64_t bytes,
|
||||||
QEMUIOVector *qiov,
|
QEMUIOVector *qiov, int flags,
|
||||||
int nb_sectors,
|
|
||||||
BlockCompletionFunc *cb,
|
BlockCompletionFunc *cb,
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
return rbd_start_aio(bs, sector_num << BDRV_SECTOR_BITS, qiov,
|
return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque,
|
||||||
(int64_t) nb_sectors << BDRV_SECTOR_BITS, cb, opaque,
|
|
||||||
RBD_AIO_READ);
|
RBD_AIO_READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *qemu_rbd_aio_writev(BlockDriverState *bs,
|
static BlockAIOCB *qemu_rbd_aio_pwritev(BlockDriverState *bs,
|
||||||
int64_t sector_num,
|
uint64_t offset, uint64_t bytes,
|
||||||
QEMUIOVector *qiov,
|
QEMUIOVector *qiov, int flags,
|
||||||
int nb_sectors,
|
|
||||||
BlockCompletionFunc *cb,
|
BlockCompletionFunc *cb,
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
return rbd_start_aio(bs, sector_num << BDRV_SECTOR_BITS, qiov,
|
return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque,
|
||||||
(int64_t) nb_sectors << BDRV_SECTOR_BITS, cb, opaque,
|
|
||||||
RBD_AIO_WRITE);
|
RBD_AIO_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1158,6 +1161,7 @@ static BlockDriver bdrv_rbd = {
|
|||||||
.format_name = "rbd",
|
.format_name = "rbd",
|
||||||
.instance_size = sizeof(BDRVRBDState),
|
.instance_size = sizeof(BDRVRBDState),
|
||||||
.bdrv_parse_filename = qemu_rbd_parse_filename,
|
.bdrv_parse_filename = qemu_rbd_parse_filename,
|
||||||
|
.bdrv_refresh_limits = qemu_rbd_refresh_limits,
|
||||||
.bdrv_file_open = qemu_rbd_open,
|
.bdrv_file_open = qemu_rbd_open,
|
||||||
.bdrv_close = qemu_rbd_close,
|
.bdrv_close = qemu_rbd_close,
|
||||||
.bdrv_reopen_prepare = qemu_rbd_reopen_prepare,
|
.bdrv_reopen_prepare = qemu_rbd_reopen_prepare,
|
||||||
@ -1170,8 +1174,8 @@ static BlockDriver bdrv_rbd = {
|
|||||||
.bdrv_truncate = qemu_rbd_truncate,
|
.bdrv_truncate = qemu_rbd_truncate,
|
||||||
.protocol_name = "rbd",
|
.protocol_name = "rbd",
|
||||||
|
|
||||||
.bdrv_aio_readv = qemu_rbd_aio_readv,
|
.bdrv_aio_preadv = qemu_rbd_aio_preadv,
|
||||||
.bdrv_aio_writev = qemu_rbd_aio_writev,
|
.bdrv_aio_pwritev = qemu_rbd_aio_pwritev,
|
||||||
|
|
||||||
#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
|
#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
|
||||||
.bdrv_aio_flush = qemu_rbd_aio_flush,
|
.bdrv_aio_flush = qemu_rbd_aio_flush,
|
||||||
|
@ -260,7 +260,8 @@ out:
|
|||||||
static coroutine_fn int replication_co_writev(BlockDriverState *bs,
|
static coroutine_fn int replication_co_writev(BlockDriverState *bs,
|
||||||
int64_t sector_num,
|
int64_t sector_num,
|
||||||
int remaining_sectors,
|
int remaining_sectors,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
BDRVReplicationState *s = bs->opaque;
|
BDRVReplicationState *s = bs->opaque;
|
||||||
QEMUIOVector hd_qiov;
|
QEMUIOVector hd_qiov;
|
||||||
@ -271,6 +272,7 @@ static coroutine_fn int replication_co_writev(BlockDriverState *bs,
|
|||||||
int ret;
|
int ret;
|
||||||
int64_t n;
|
int64_t n;
|
||||||
|
|
||||||
|
assert(!flags);
|
||||||
ret = replication_get_io_status(s);
|
ret = replication_get_io_status(s);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -2614,13 +2614,15 @@ static void sd_aio_complete(SheepdogAIOCB *acb)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int sd_co_writev(BlockDriverState *bs, int64_t sector_num,
|
static coroutine_fn int sd_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
int nb_sectors, QEMUIOVector *qiov,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
SheepdogAIOCB acb;
|
SheepdogAIOCB acb;
|
||||||
int ret;
|
int ret;
|
||||||
int64_t offset = (sector_num + nb_sectors) * BDRV_SECTOR_SIZE;
|
int64_t offset = (sector_num + nb_sectors) * BDRV_SECTOR_SIZE;
|
||||||
BDRVSheepdogState *s = bs->opaque;
|
BDRVSheepdogState *s = bs->opaque;
|
||||||
|
|
||||||
|
assert(!flags);
|
||||||
if (offset > s->inode.vdi_size) {
|
if (offset > s->inode.vdi_size) {
|
||||||
ret = sd_truncate(bs, offset, PREALLOC_MODE_OFF, NULL);
|
ret = sd_truncate(bs, offset, PREALLOC_MODE_OFF, NULL);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -1164,11 +1164,13 @@ static int ssh_write(BDRVSSHState *s, BlockDriverState *bs,
|
|||||||
|
|
||||||
static coroutine_fn int ssh_co_writev(BlockDriverState *bs,
|
static coroutine_fn int ssh_co_writev(BlockDriverState *bs,
|
||||||
int64_t sector_num,
|
int64_t sector_num,
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
int nb_sectors, QEMUIOVector *qiov,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
BDRVSSHState *s = bs->opaque;
|
BDRVSSHState *s = bs->opaque;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
assert(!flags);
|
||||||
qemu_co_mutex_lock(&s->lock);
|
qemu_co_mutex_lock(&s->lock);
|
||||||
ret = ssh_write(s, bs, sector_num * BDRV_SECTOR_SIZE,
|
ret = ssh_write(s, bs, sector_num * BDRV_SECTOR_SIZE,
|
||||||
nb_sectors * BDRV_SECTOR_SIZE, qiov);
|
nb_sectors * BDRV_SECTOR_SIZE, qiov);
|
||||||
|
@ -29,11 +29,8 @@ enum {
|
|||||||
STREAM_BUFFER_SIZE = 512 * 1024, /* in bytes */
|
STREAM_BUFFER_SIZE = 512 * 1024, /* in bytes */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SLICE_TIME 100000000ULL /* ns */
|
|
||||||
|
|
||||||
typedef struct StreamBlockJob {
|
typedef struct StreamBlockJob {
|
||||||
BlockJob common;
|
BlockJob common;
|
||||||
RateLimit limit;
|
|
||||||
BlockDriverState *base;
|
BlockDriverState *base;
|
||||||
BlockdevOnError on_error;
|
BlockdevOnError on_error;
|
||||||
char *backing_file_str;
|
char *backing_file_str;
|
||||||
@ -107,6 +104,7 @@ static void coroutine_fn stream_run(void *opaque)
|
|||||||
BlockBackend *blk = s->common.blk;
|
BlockBackend *blk = s->common.blk;
|
||||||
BlockDriverState *bs = blk_bs(blk);
|
BlockDriverState *bs = blk_bs(blk);
|
||||||
BlockDriverState *base = s->base;
|
BlockDriverState *base = s->base;
|
||||||
|
int64_t len;
|
||||||
int64_t offset = 0;
|
int64_t offset = 0;
|
||||||
uint64_t delay_ns = 0;
|
uint64_t delay_ns = 0;
|
||||||
int error = 0;
|
int error = 0;
|
||||||
@ -118,11 +116,12 @@ static void coroutine_fn stream_run(void *opaque)
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->common.len = bdrv_getlength(bs);
|
len = bdrv_getlength(bs);
|
||||||
if (s->common.len < 0) {
|
if (len < 0) {
|
||||||
ret = s->common.len;
|
ret = len;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
block_job_progress_set_remaining(&s->common, len);
|
||||||
|
|
||||||
buf = qemu_blockalign(bs, STREAM_BUFFER_SIZE);
|
buf = qemu_blockalign(bs, STREAM_BUFFER_SIZE);
|
||||||
|
|
||||||
@ -135,7 +134,7 @@ static void coroutine_fn stream_run(void *opaque)
|
|||||||
bdrv_enable_copy_on_read(bs);
|
bdrv_enable_copy_on_read(bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( ; offset < s->common.len; offset += n) {
|
for ( ; offset < len; offset += n) {
|
||||||
bool copy;
|
bool copy;
|
||||||
|
|
||||||
/* 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
|
||||||
@ -159,7 +158,7 @@ static void coroutine_fn stream_run(void *opaque)
|
|||||||
|
|
||||||
/* Finish early if end of backing file has been reached */
|
/* Finish early if end of backing file has been reached */
|
||||||
if (ret == 0 && n == 0) {
|
if (ret == 0 && n == 0) {
|
||||||
n = s->common.len - offset;
|
n = len - offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
copy = (ret == 1);
|
copy = (ret == 1);
|
||||||
@ -185,9 +184,9 @@ static void coroutine_fn stream_run(void *opaque)
|
|||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
||||||
/* Publish progress */
|
/* Publish progress */
|
||||||
s->common.offset += n;
|
block_job_progress_update(&s->common, n);
|
||||||
if (copy && s->common.speed) {
|
if (copy) {
|
||||||
delay_ns = ratelimit_calculate_delay(&s->limit, n);
|
delay_ns = block_job_ratelimit_get_delay(&s->common, n);
|
||||||
} else {
|
} else {
|
||||||
delay_ns = 0;
|
delay_ns = 0;
|
||||||
}
|
}
|
||||||
@ -209,21 +208,9 @@ out:
|
|||||||
block_job_defer_to_main_loop(&s->common, stream_complete, data);
|
block_job_defer_to_main_loop(&s->common, stream_complete, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stream_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|
||||||
{
|
|
||||||
StreamBlockJob *s = container_of(job, StreamBlockJob, common);
|
|
||||||
|
|
||||||
if (speed < 0) {
|
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const BlockJobDriver stream_job_driver = {
|
static const BlockJobDriver stream_job_driver = {
|
||||||
.instance_size = sizeof(StreamBlockJob),
|
.instance_size = sizeof(StreamBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_STREAM,
|
.job_type = BLOCK_JOB_TYPE_STREAM,
|
||||||
.set_speed = stream_set_speed,
|
|
||||||
.start = stream_run,
|
.start = stream_run,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -81,8 +81,10 @@ static int throttle_open(BlockDriverState *bs, QDict *options,
|
|||||||
if (!bs->file) {
|
if (!bs->file) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
bs->supported_write_flags = bs->file->bs->supported_write_flags;
|
bs->supported_write_flags = bs->file->bs->supported_write_flags |
|
||||||
bs->supported_zero_flags = bs->file->bs->supported_zero_flags;
|
BDRV_REQ_WRITE_UNCHANGED;
|
||||||
|
bs->supported_zero_flags = bs->file->bs->supported_zero_flags |
|
||||||
|
BDRV_REQ_WRITE_UNCHANGED;
|
||||||
|
|
||||||
return throttle_configure_tgm(bs, tgm, options, errp);
|
return throttle_configure_tgm(bs, tgm, options, errp);
|
||||||
}
|
}
|
||||||
|
@ -1226,7 +1226,8 @@ int vhdx_user_visible_write(BlockDriverState *bs, BDRVVHDXState *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int vhdx_co_writev(BlockDriverState *bs, int64_t sector_num,
|
static coroutine_fn int vhdx_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
int nb_sectors, QEMUIOVector *qiov)
|
int nb_sectors, QEMUIOVector *qiov,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
int ret = -ENOTSUP;
|
int ret = -ENOTSUP;
|
||||||
BDRVVHDXState *s = bs->opaque;
|
BDRVVHDXState *s = bs->opaque;
|
||||||
@ -1242,6 +1243,7 @@ static coroutine_fn int vhdx_co_writev(BlockDriverState *bs, int64_t sector_num,
|
|||||||
uint64_t bat_prior_offset = 0;
|
uint64_t bat_prior_offset = 0;
|
||||||
bool bat_update = false;
|
bool bat_update = false;
|
||||||
|
|
||||||
|
assert(!flags);
|
||||||
qemu_iovec_init(&hd_qiov, qiov->niov);
|
qemu_iovec_init(&hd_qiov, qiov->niov);
|
||||||
|
|
||||||
qemu_co_mutex_lock(&s->lock);
|
qemu_co_mutex_lock(&s->lock);
|
||||||
|
41
block/vxhs.c
41
block/vxhs.c
@ -216,6 +216,12 @@ static void vxhs_parse_filename(const char *filename, QDict *options,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void vxhs_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||||
|
{
|
||||||
|
/* XXX Does VXHS support AIO on less than 512-byte alignment? */
|
||||||
|
bs->bl.request_alignment = 512;
|
||||||
|
}
|
||||||
|
|
||||||
static int vxhs_init_and_ref(void)
|
static int vxhs_init_and_ref(void)
|
||||||
{
|
{
|
||||||
if (vxhs_ref++ == 0) {
|
if (vxhs_ref++ == 0) {
|
||||||
@ -424,21 +430,17 @@ static const AIOCBInfo vxhs_aiocb_info = {
|
|||||||
* and is passed to QNIO. When QNIO completes the work,
|
* and is passed to QNIO. When QNIO completes the work,
|
||||||
* it will be passed back through the callback.
|
* it will be passed back through the callback.
|
||||||
*/
|
*/
|
||||||
static BlockAIOCB *vxhs_aio_rw(BlockDriverState *bs, int64_t sector_num,
|
static BlockAIOCB *vxhs_aio_rw(BlockDriverState *bs, uint64_t offset,
|
||||||
QEMUIOVector *qiov, int nb_sectors,
|
QEMUIOVector *qiov, uint64_t size,
|
||||||
BlockCompletionFunc *cb, void *opaque,
|
BlockCompletionFunc *cb, void *opaque,
|
||||||
VDISKAIOCmd iodir)
|
VDISKAIOCmd iodir)
|
||||||
{
|
{
|
||||||
VXHSAIOCB *acb = NULL;
|
VXHSAIOCB *acb = NULL;
|
||||||
BDRVVXHSState *s = bs->opaque;
|
BDRVVXHSState *s = bs->opaque;
|
||||||
size_t size;
|
|
||||||
uint64_t offset;
|
|
||||||
int iio_flags = 0;
|
int iio_flags = 0;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
void *dev_handle = s->vdisk_hostinfo.dev_handle;
|
void *dev_handle = s->vdisk_hostinfo.dev_handle;
|
||||||
|
|
||||||
offset = sector_num * BDRV_SECTOR_SIZE;
|
|
||||||
size = nb_sectors * BDRV_SECTOR_SIZE;
|
|
||||||
acb = qemu_aio_get(&vxhs_aiocb_info, bs, cb, opaque);
|
acb = qemu_aio_get(&vxhs_aiocb_info, bs, cb, opaque);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -451,11 +453,11 @@ static BlockAIOCB *vxhs_aio_rw(BlockDriverState *bs, int64_t sector_num,
|
|||||||
switch (iodir) {
|
switch (iodir) {
|
||||||
case VDISK_AIO_WRITE:
|
case VDISK_AIO_WRITE:
|
||||||
ret = iio_writev(dev_handle, acb, qiov->iov, qiov->niov,
|
ret = iio_writev(dev_handle, acb, qiov->iov, qiov->niov,
|
||||||
offset, (uint64_t)size, iio_flags);
|
offset, size, iio_flags);
|
||||||
break;
|
break;
|
||||||
case VDISK_AIO_READ:
|
case VDISK_AIO_READ:
|
||||||
ret = iio_readv(dev_handle, acb, qiov->iov, qiov->niov,
|
ret = iio_readv(dev_handle, acb, qiov->iov, qiov->niov,
|
||||||
offset, (uint64_t)size, iio_flags);
|
offset, size, iio_flags);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
trace_vxhs_aio_rw_invalid(iodir);
|
trace_vxhs_aio_rw_invalid(iodir);
|
||||||
@ -474,22 +476,20 @@ errout:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *vxhs_aio_readv(BlockDriverState *bs,
|
static BlockAIOCB *vxhs_aio_preadv(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov,
|
uint64_t offset, uint64_t bytes,
|
||||||
int nb_sectors,
|
QEMUIOVector *qiov, int flags,
|
||||||
BlockCompletionFunc *cb, void *opaque)
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
return vxhs_aio_rw(bs, sector_num, qiov, nb_sectors, cb,
|
return vxhs_aio_rw(bs, offset, qiov, bytes, cb, opaque, VDISK_AIO_READ);
|
||||||
opaque, VDISK_AIO_READ);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *vxhs_aio_writev(BlockDriverState *bs,
|
static BlockAIOCB *vxhs_aio_pwritev(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov,
|
uint64_t offset, uint64_t bytes,
|
||||||
int nb_sectors,
|
QEMUIOVector *qiov, int flags,
|
||||||
BlockCompletionFunc *cb, void *opaque)
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
return vxhs_aio_rw(bs, sector_num, qiov, nb_sectors,
|
return vxhs_aio_rw(bs, offset, qiov, bytes, cb, opaque, VDISK_AIO_WRITE);
|
||||||
cb, opaque, VDISK_AIO_WRITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void vxhs_close(BlockDriverState *bs)
|
static void vxhs_close(BlockDriverState *bs)
|
||||||
@ -561,10 +561,11 @@ static BlockDriver bdrv_vxhs = {
|
|||||||
.instance_size = sizeof(BDRVVXHSState),
|
.instance_size = sizeof(BDRVVXHSState),
|
||||||
.bdrv_file_open = vxhs_open,
|
.bdrv_file_open = vxhs_open,
|
||||||
.bdrv_parse_filename = vxhs_parse_filename,
|
.bdrv_parse_filename = vxhs_parse_filename,
|
||||||
|
.bdrv_refresh_limits = vxhs_refresh_limits,
|
||||||
.bdrv_close = vxhs_close,
|
.bdrv_close = vxhs_close,
|
||||||
.bdrv_getlength = vxhs_getlength,
|
.bdrv_getlength = vxhs_getlength,
|
||||||
.bdrv_aio_readv = vxhs_aio_readv,
|
.bdrv_aio_preadv = vxhs_aio_preadv,
|
||||||
.bdrv_aio_writev = vxhs_aio_writev,
|
.bdrv_aio_pwritev = vxhs_aio_pwritev,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_vxhs_init(void)
|
static void bdrv_vxhs_init(void)
|
||||||
|
@ -112,15 +112,14 @@ static const AIOCBInfo win32_aiocb_info = {
|
|||||||
|
|
||||||
BlockAIOCB *win32_aio_submit(BlockDriverState *bs,
|
BlockAIOCB *win32_aio_submit(BlockDriverState *bs,
|
||||||
QEMUWin32AIOState *aio, HANDLE hfile,
|
QEMUWin32AIOState *aio, HANDLE hfile,
|
||||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov,
|
||||||
BlockCompletionFunc *cb, void *opaque, int type)
|
BlockCompletionFunc *cb, void *opaque, int type)
|
||||||
{
|
{
|
||||||
struct QEMUWin32AIOCB *waiocb;
|
struct QEMUWin32AIOCB *waiocb;
|
||||||
uint64_t offset = sector_num * 512;
|
|
||||||
DWORD rc;
|
DWORD rc;
|
||||||
|
|
||||||
waiocb = qemu_aio_get(&win32_aiocb_info, bs, cb, opaque);
|
waiocb = qemu_aio_get(&win32_aiocb_info, bs, cb, opaque);
|
||||||
waiocb->nbytes = nb_sectors * 512;
|
waiocb->nbytes = bytes;
|
||||||
waiocb->qiov = qiov;
|
waiocb->qiov = qiov;
|
||||||
waiocb->is_read = (type == QEMU_AIO_READ);
|
waiocb->is_read = (type == QEMU_AIO_READ);
|
||||||
|
|
||||||
|
40
blockjob.c
40
blockjob.c
@ -359,6 +359,11 @@ static bool block_job_started(BlockJob *job)
|
|||||||
return job->co;
|
return job->co;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BlockJobDriver *block_job_driver(BlockJob *job)
|
||||||
|
{
|
||||||
|
return job->driver;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All jobs must allow a pause point before entering their job proper. This
|
* All jobs must allow a pause point before entering their job proper. This
|
||||||
* ensures that jobs can be paused prior to being started, then resumed later.
|
* ensures that jobs can be paused prior to being started, then resumed later.
|
||||||
@ -659,22 +664,18 @@ static bool block_job_timer_pending(BlockJob *job)
|
|||||||
|
|
||||||
void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
|
||||||
int64_t old_speed = job->speed;
|
int64_t old_speed = job->speed;
|
||||||
|
|
||||||
if (!job->driver->set_speed) {
|
|
||||||
error_setg(errp, QERR_UNSUPPORTED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (block_job_apply_verb(job, BLOCK_JOB_VERB_SET_SPEED, errp)) {
|
if (block_job_apply_verb(job, BLOCK_JOB_VERB_SET_SPEED, errp)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
job->driver->set_speed(job, speed, &local_err);
|
if (speed < 0) {
|
||||||
if (local_err) {
|
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ratelimit_set_speed(&job->limit, speed, BLOCK_JOB_SLICE_TIME);
|
||||||
|
|
||||||
job->speed = speed;
|
job->speed = speed;
|
||||||
if (speed && speed <= old_speed) {
|
if (speed && speed <= old_speed) {
|
||||||
return;
|
return;
|
||||||
@ -684,6 +685,15 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|||||||
block_job_enter_cond(job, block_job_timer_pending);
|
block_job_enter_cond(job, block_job_timer_pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
|
||||||
|
{
|
||||||
|
if (!job->speed) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ratelimit_calculate_delay(&job->limit, n);
|
||||||
|
}
|
||||||
|
|
||||||
void block_job_complete(BlockJob *job, Error **errp)
|
void block_job_complete(BlockJob *job, Error **errp)
|
||||||
{
|
{
|
||||||
/* Should not be reachable via external interface for internal jobs */
|
/* Should not be reachable via external interface for internal jobs */
|
||||||
@ -702,7 +712,7 @@ void block_job_complete(BlockJob *job, Error **errp)
|
|||||||
|
|
||||||
void block_job_finalize(BlockJob *job, Error **errp)
|
void block_job_finalize(BlockJob *job, Error **errp)
|
||||||
{
|
{
|
||||||
assert(job && job->id && job->txn);
|
assert(job && job->id);
|
||||||
if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
|
if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -810,6 +820,16 @@ int block_job_complete_sync(BlockJob *job, Error **errp)
|
|||||||
return block_job_finish_sync(job, &block_job_complete, errp);
|
return block_job_finish_sync(job, &block_job_complete, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void block_job_progress_update(BlockJob *job, uint64_t done)
|
||||||
|
{
|
||||||
|
job->offset += done;
|
||||||
|
}
|
||||||
|
|
||||||
|
void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining)
|
||||||
|
{
|
||||||
|
job->len = job->offset + remaining;
|
||||||
|
}
|
||||||
|
|
||||||
BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
|
BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
|
||||||
{
|
{
|
||||||
BlockJobInfo *info;
|
BlockJobInfo *info;
|
||||||
@ -831,6 +851,8 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
|
|||||||
info->status = job->status;
|
info->status = job->status;
|
||||||
info->auto_finalize = job->auto_finalize;
|
info->auto_finalize = job->auto_finalize;
|
||||||
info->auto_dismiss = job->auto_dismiss;
|
info->auto_dismiss = job->auto_dismiss;
|
||||||
|
info->has_error = job->ret != 0;
|
||||||
|
info->error = job->ret ? g_strdup(strerror(-job->ret)) : NULL;
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,10 +400,10 @@ L2 table entry:
|
|||||||
62: 0 for standard clusters
|
62: 0 for standard clusters
|
||||||
1 for compressed clusters
|
1 for compressed clusters
|
||||||
|
|
||||||
63: 0 for a cluster that is unused or requires COW, 1 if its
|
63: 0 for clusters that are unused, compressed or require COW.
|
||||||
refcount is exactly one. This information is only accurate
|
1 for standard clusters whose refcount is exactly one.
|
||||||
in L2 tables that are reachable from the active L1
|
This information is only accurate in L2 tables
|
||||||
table.
|
that are reachable from the active L1 table.
|
||||||
|
|
||||||
Standard Cluster Descriptor:
|
Standard Cluster Descriptor:
|
||||||
|
|
||||||
|
@ -116,31 +116,30 @@ There are three options available, and all of them take bytes:
|
|||||||
"refcount-cache-size": maximum size of the refcount block cache
|
"refcount-cache-size": maximum size of the refcount block cache
|
||||||
"cache-size": maximum size of both caches combined
|
"cache-size": maximum size of both caches combined
|
||||||
|
|
||||||
There are two things that need to be taken into account:
|
There are a few things that need to be taken into account:
|
||||||
|
|
||||||
- Both caches must have a size that is a multiple of the cluster size
|
- Both caches must have a size that is a multiple of the cluster size
|
||||||
(or the cache entry size: see "Using smaller cache sizes" below).
|
(or the cache entry size: see "Using smaller cache sizes" below).
|
||||||
|
|
||||||
- If you only set one of the options above, QEMU will automatically
|
- The default L2 cache size is 8 clusters or 1MB (whichever is more),
|
||||||
adjust the others so that the L2 cache is 4 times bigger than the
|
and the minimum is 2 clusters (or 2 cache entries, see below).
|
||||||
refcount cache.
|
|
||||||
|
|
||||||
This means that these options are equivalent:
|
- The default (and minimum) refcount cache size is 4 clusters.
|
||||||
|
|
||||||
-drive file=hd.qcow2,l2-cache-size=2097152
|
- If only "cache-size" is specified then QEMU will assign as much
|
||||||
-drive file=hd.qcow2,refcount-cache-size=524288
|
memory as possible to the L2 cache before increasing the refcount
|
||||||
-drive file=hd.qcow2,cache-size=2621440
|
cache size.
|
||||||
|
|
||||||
The reason for this 1/4 ratio is to ensure that both caches cover the
|
Unlike L2 tables, refcount blocks are not used during normal I/O but
|
||||||
same amount of disk space. Note however that this is only valid with
|
only during allocations and internal snapshots. In most cases they are
|
||||||
the default value of refcount_bits (16). If you are using a different
|
accessed sequentially (even during random guest I/O) so increasing the
|
||||||
value you might want to calculate both cache sizes yourself since QEMU
|
refcount cache size won't have any measurable effect in performance
|
||||||
will always use the same 1/4 ratio.
|
(this can change if you are using internal snapshots, so you may want
|
||||||
|
to think about increasing the cache size if you use them heavily).
|
||||||
|
|
||||||
It's also worth mentioning that there's no strict need for both caches
|
Before QEMU 2.12 the refcount cache had a default size of 1/4 of the
|
||||||
to cover the same amount of disk space. The refcount cache is used
|
L2 cache size. This resulted in unnecessarily large caches, so now the
|
||||||
much less often than the L2 cache, so it's perfectly reasonable to
|
refcount cache is as small as possible unless overridden by the user.
|
||||||
keep it small.
|
|
||||||
|
|
||||||
|
|
||||||
Using smaller cache entries
|
Using smaller cache entries
|
||||||
|
@ -1647,7 +1647,8 @@ ETEXI
|
|||||||
STEXI
|
STEXI
|
||||||
@item block_set_io_throttle @var{device} @var{bps} @var{bps_rd} @var{bps_wr} @var{iops} @var{iops_rd} @var{iops_wr}
|
@item block_set_io_throttle @var{device} @var{bps} @var{bps_rd} @var{bps_wr} @var{iops} @var{iops_rd} @var{iops_wr}
|
||||||
@findex block_set_io_throttle
|
@findex block_set_io_throttle
|
||||||
Change I/O throttle limits for a block drive to @var{bps} @var{bps_rd} @var{bps_wr} @var{iops} @var{iops_rd} @var{iops_wr}
|
Change I/O throttle limits for a block drive to @var{bps} @var{bps_rd} @var{bps_wr} @var{iops} @var{iops_rd} @var{iops_wr}.
|
||||||
|
@var{device} can be a block device name, a qdev ID or a QOM path.
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
{
|
{
|
||||||
|
14
hmp.c
14
hmp.c
@ -1789,9 +1789,8 @@ void hmp_change(Monitor *mon, const QDict *qdict)
|
|||||||
void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict)
|
void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict)
|
||||||
{
|
{
|
||||||
Error *err = NULL;
|
Error *err = NULL;
|
||||||
|
char *device = (char *) qdict_get_str(qdict, "device");
|
||||||
BlockIOThrottle throttle = {
|
BlockIOThrottle throttle = {
|
||||||
.has_device = true,
|
|
||||||
.device = (char *) qdict_get_str(qdict, "device"),
|
|
||||||
.bps = qdict_get_int(qdict, "bps"),
|
.bps = qdict_get_int(qdict, "bps"),
|
||||||
.bps_rd = qdict_get_int(qdict, "bps_rd"),
|
.bps_rd = qdict_get_int(qdict, "bps_rd"),
|
||||||
.bps_wr = qdict_get_int(qdict, "bps_wr"),
|
.bps_wr = qdict_get_int(qdict, "bps_wr"),
|
||||||
@ -1800,6 +1799,17 @@ void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict)
|
|||||||
.iops_wr = qdict_get_int(qdict, "iops_wr"),
|
.iops_wr = qdict_get_int(qdict, "iops_wr"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* qmp_block_set_io_throttle has separate parameters for the
|
||||||
|
* (deprecated) block device name and the qdev ID but the HMP
|
||||||
|
* version has only one, so we must decide which one to pass. */
|
||||||
|
if (blk_by_name(device)) {
|
||||||
|
throttle.has_device = true;
|
||||||
|
throttle.device = device;
|
||||||
|
} else {
|
||||||
|
throttle.has_id = true;
|
||||||
|
throttle.id = device;
|
||||||
|
}
|
||||||
|
|
||||||
qmp_block_set_io_throttle(&throttle, &err);
|
qmp_block_set_io_throttle(&throttle, &err);
|
||||||
hmp_handle_error(mon, &err);
|
hmp_handle_error(mon, &err);
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,12 @@ typedef enum {
|
|||||||
BDRV_REQ_FUA = 0x10,
|
BDRV_REQ_FUA = 0x10,
|
||||||
BDRV_REQ_WRITE_COMPRESSED = 0x20,
|
BDRV_REQ_WRITE_COMPRESSED = 0x20,
|
||||||
|
|
||||||
|
/* Signifies that this write request will not change the visible disk
|
||||||
|
* content. */
|
||||||
|
BDRV_REQ_WRITE_UNCHANGED = 0x40,
|
||||||
|
|
||||||
/* Mask of valid flags */
|
/* Mask of valid flags */
|
||||||
BDRV_REQ_MASK = 0x3f,
|
BDRV_REQ_MASK = 0x7f,
|
||||||
} BdrvRequestFlags;
|
} BdrvRequestFlags;
|
||||||
|
|
||||||
typedef struct BlockSizes {
|
typedef struct BlockSizes {
|
||||||
@ -205,6 +209,9 @@ enum {
|
|||||||
* This permission (which is weaker than BLK_PERM_WRITE) is both enough and
|
* This permission (which is weaker than BLK_PERM_WRITE) is both enough and
|
||||||
* required for writes to the block node when the caller promises that
|
* required for writes to the block node when the caller promises that
|
||||||
* the visible disk content doesn't change.
|
* the visible disk content doesn't change.
|
||||||
|
*
|
||||||
|
* As the BLK_PERM_WRITE permission is strictly stronger, either is
|
||||||
|
* sufficient to perform an unchanging write.
|
||||||
*/
|
*/
|
||||||
BLK_PERM_WRITE_UNCHANGED = 0x04,
|
BLK_PERM_WRITE_UNCHANGED = 0x04,
|
||||||
|
|
||||||
|
@ -141,11 +141,11 @@ struct BlockDriver {
|
|||||||
void (*bdrv_refresh_filename)(BlockDriverState *bs, QDict *options);
|
void (*bdrv_refresh_filename)(BlockDriverState *bs, QDict *options);
|
||||||
|
|
||||||
/* aio */
|
/* aio */
|
||||||
BlockAIOCB *(*bdrv_aio_readv)(BlockDriverState *bs,
|
BlockAIOCB *(*bdrv_aio_preadv)(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags,
|
||||||
BlockCompletionFunc *cb, void *opaque);
|
BlockCompletionFunc *cb, void *opaque);
|
||||||
BlockAIOCB *(*bdrv_aio_writev)(BlockDriverState *bs,
|
BlockAIOCB *(*bdrv_aio_pwritev)(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags,
|
||||||
BlockCompletionFunc *cb, void *opaque);
|
BlockCompletionFunc *cb, void *opaque);
|
||||||
BlockAIOCB *(*bdrv_aio_flush)(BlockDriverState *bs,
|
BlockAIOCB *(*bdrv_aio_flush)(BlockDriverState *bs,
|
||||||
BlockCompletionFunc *cb, void *opaque);
|
BlockCompletionFunc *cb, void *opaque);
|
||||||
@ -174,8 +174,6 @@ struct BlockDriver {
|
|||||||
int coroutine_fn (*bdrv_co_preadv)(BlockDriverState *bs,
|
int coroutine_fn (*bdrv_co_preadv)(BlockDriverState *bs,
|
||||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags);
|
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags);
|
||||||
int coroutine_fn (*bdrv_co_writev)(BlockDriverState *bs,
|
int coroutine_fn (*bdrv_co_writev)(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov);
|
|
||||||
int coroutine_fn (*bdrv_co_writev_flags)(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov, int flags);
|
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov, int flags);
|
||||||
/**
|
/**
|
||||||
* @offset: position in bytes to write at
|
* @offset: position in bytes to write at
|
||||||
@ -658,10 +656,24 @@ struct BlockDriverState {
|
|||||||
/* I/O Limits */
|
/* I/O Limits */
|
||||||
BlockLimits bl;
|
BlockLimits bl;
|
||||||
|
|
||||||
/* Flags honored during pwrite (so far: BDRV_REQ_FUA) */
|
/* Flags honored during pwrite (so far: BDRV_REQ_FUA,
|
||||||
|
* BDRV_REQ_WRITE_UNCHANGED).
|
||||||
|
* If a driver does not support BDRV_REQ_WRITE_UNCHANGED, those
|
||||||
|
* writes will be issued as normal writes without the flag set.
|
||||||
|
* This is important to note for drivers that do not explicitly
|
||||||
|
* request a WRITE permission for their children and instead take
|
||||||
|
* the same permissions as their parent did (this is commonly what
|
||||||
|
* block filters do). Such drivers have to be aware that the
|
||||||
|
* parent may have taken a WRITE_UNCHANGED permission only and is
|
||||||
|
* issuing such requests. Drivers either must make sure that
|
||||||
|
* these requests do not result in plain WRITE accesses (usually
|
||||||
|
* by supporting BDRV_REQ_WRITE_UNCHANGED, and then forwarding
|
||||||
|
* every incoming write request as-is, including potentially that
|
||||||
|
* flag), or they have to explicitly take the WRITE permission for
|
||||||
|
* their children. */
|
||||||
unsigned int supported_write_flags;
|
unsigned int supported_write_flags;
|
||||||
/* Flags honored during pwrite_zeroes (so far: BDRV_REQ_FUA,
|
/* Flags honored during pwrite_zeroes (so far: BDRV_REQ_FUA,
|
||||||
* BDRV_REQ_MAY_UNMAP) */
|
* BDRV_REQ_MAY_UNMAP, BDRV_REQ_WRITE_UNCHANGED) */
|
||||||
unsigned int supported_zero_flags;
|
unsigned int supported_zero_flags;
|
||||||
|
|
||||||
/* the following member gives a name to every node on the bs graph. */
|
/* the following member gives a name to every node on the bs graph. */
|
||||||
|
@ -27,6 +27,9 @@
|
|||||||
#define BLOCKJOB_H
|
#define BLOCKJOB_H
|
||||||
|
|
||||||
#include "block/block.h"
|
#include "block/block.h"
|
||||||
|
#include "qemu/ratelimit.h"
|
||||||
|
|
||||||
|
#define BLOCK_JOB_SLICE_TIME 100000000ULL /* ns */
|
||||||
|
|
||||||
typedef struct BlockJobDriver BlockJobDriver;
|
typedef struct BlockJobDriver BlockJobDriver;
|
||||||
typedef struct BlockJobTxn BlockJobTxn;
|
typedef struct BlockJobTxn BlockJobTxn;
|
||||||
@ -118,6 +121,9 @@ typedef struct BlockJob {
|
|||||||
/** Speed that was set with @block_job_set_speed. */
|
/** Speed that was set with @block_job_set_speed. */
|
||||||
int64_t speed;
|
int64_t speed;
|
||||||
|
|
||||||
|
/** Rate limiting data structure for implementing @speed. */
|
||||||
|
RateLimit limit;
|
||||||
|
|
||||||
/** The completion function that will be called when the job completes. */
|
/** The completion function that will be called when the job completes. */
|
||||||
BlockCompletionFunc *cb;
|
BlockCompletionFunc *cb;
|
||||||
|
|
||||||
@ -277,6 +283,25 @@ void block_job_finalize(BlockJob *job, Error **errp);
|
|||||||
*/
|
*/
|
||||||
void block_job_dismiss(BlockJob **job, Error **errp);
|
void block_job_dismiss(BlockJob **job, Error **errp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* block_job_progress_update:
|
||||||
|
* @job: The job that has made progress
|
||||||
|
* @done: How much progress the job made
|
||||||
|
*
|
||||||
|
* Updates the progress counter of the job.
|
||||||
|
*/
|
||||||
|
void block_job_progress_update(BlockJob *job, uint64_t done);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* block_job_progress_set_remaining:
|
||||||
|
* @job: The job whose expected progress end value is set
|
||||||
|
* @remaining: Expected end value of the progress counter of the job
|
||||||
|
*
|
||||||
|
* Sets the expected end value of the progress counter of a job so that a
|
||||||
|
* completion percentage can be calculated when the progress is updated.
|
||||||
|
*/
|
||||||
|
void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* block_job_query:
|
* block_job_query:
|
||||||
* @job: The job to get information about.
|
* @job: The job to get information about.
|
||||||
@ -427,4 +452,11 @@ void block_job_txn_add_job(BlockJobTxn *txn, BlockJob *job);
|
|||||||
*/
|
*/
|
||||||
bool block_job_is_internal(BlockJob *job);
|
bool block_job_is_internal(BlockJob *job);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* block_job_driver:
|
||||||
|
*
|
||||||
|
* Returns the driver associated with a block job.
|
||||||
|
*/
|
||||||
|
const BlockJobDriver *block_job_driver(BlockJob *job);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -41,9 +41,6 @@ struct BlockJobDriver {
|
|||||||
/** String describing the operation, part of query-block-jobs QMP API */
|
/** String describing the operation, part of query-block-jobs QMP API */
|
||||||
BlockJobType job_type;
|
BlockJobType job_type;
|
||||||
|
|
||||||
/** Optional callback for job types that support setting a speed limit */
|
|
||||||
void (*set_speed)(BlockJob *job, int64_t speed, Error **errp);
|
|
||||||
|
|
||||||
/** Mandatory: Entrypoint for the Coroutine. */
|
/** Mandatory: Entrypoint for the Coroutine. */
|
||||||
CoroutineEntry *start;
|
CoroutineEntry *start;
|
||||||
|
|
||||||
@ -168,6 +165,14 @@ void block_job_sleep_ns(BlockJob *job, int64_t ns);
|
|||||||
*/
|
*/
|
||||||
void block_job_yield(BlockJob *job);
|
void block_job_yield(BlockJob *job);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* block_job_ratelimit_get_delay:
|
||||||
|
*
|
||||||
|
* Calculate and return delay for the next request in ns. See the documentation
|
||||||
|
* of ratelimit_calculate_delay() for details.
|
||||||
|
*/
|
||||||
|
int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* block_job_early_fail:
|
* block_job_early_fail:
|
||||||
* @bs: The block device.
|
* @bs: The block device.
|
||||||
|
@ -57,7 +57,7 @@ void win32_aio_cleanup(QEMUWin32AIOState *aio);
|
|||||||
int win32_aio_attach(QEMUWin32AIOState *aio, HANDLE hfile);
|
int win32_aio_attach(QEMUWin32AIOState *aio, HANDLE hfile);
|
||||||
BlockAIOCB *win32_aio_submit(BlockDriverState *bs,
|
BlockAIOCB *win32_aio_submit(BlockDriverState *bs,
|
||||||
QEMUWin32AIOState *aio, HANDLE hfile,
|
QEMUWin32AIOState *aio, HANDLE hfile,
|
||||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov,
|
||||||
BlockCompletionFunc *cb, void *opaque, int type);
|
BlockCompletionFunc *cb, void *opaque, int type);
|
||||||
void win32_aio_detach_aio_context(QEMUWin32AIOState *aio,
|
void win32_aio_detach_aio_context(QEMUWin32AIOState *aio,
|
||||||
AioContext *old_context);
|
AioContext *old_context);
|
||||||
|
@ -1172,6 +1172,9 @@
|
|||||||
# @auto-dismiss: Job will dismiss itself when CONCLUDED, moving to the NULL
|
# @auto-dismiss: Job will dismiss itself when CONCLUDED, moving to the NULL
|
||||||
# state and disappearing from the query list. (since 2.12)
|
# state and disappearing from the query list. (since 2.12)
|
||||||
#
|
#
|
||||||
|
# @error: Error information if the job did not complete successfully.
|
||||||
|
# Not set if the job completed successfully. (since 2.12.1)
|
||||||
|
#
|
||||||
# Since: 1.1
|
# Since: 1.1
|
||||||
##
|
##
|
||||||
{ 'struct': 'BlockJobInfo',
|
{ 'struct': 'BlockJobInfo',
|
||||||
@ -1179,7 +1182,8 @@
|
|||||||
'offset': 'int', 'busy': 'bool', 'paused': 'bool', 'speed': 'int',
|
'offset': 'int', 'busy': 'bool', 'paused': 'bool', 'speed': 'int',
|
||||||
'io-status': 'BlockDeviceIoStatus', 'ready': 'bool',
|
'io-status': 'BlockDeviceIoStatus', 'ready': 'bool',
|
||||||
'status': 'BlockJobStatus',
|
'status': 'BlockJobStatus',
|
||||||
'auto-finalize': 'bool', 'auto-dismiss': 'bool' } }
|
'auto-finalize': 'bool', 'auto-dismiss': 'bool',
|
||||||
|
'*error': 'str' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @query-block-jobs:
|
# @query-block-jobs:
|
||||||
@ -2506,11 +2510,12 @@
|
|||||||
# @vxhs: Since 2.10
|
# @vxhs: Since 2.10
|
||||||
# @throttle: Since 2.11
|
# @throttle: Since 2.11
|
||||||
# @nvme: Since 2.12
|
# @nvme: Since 2.12
|
||||||
|
# @copy-on-read: Since 2.13
|
||||||
#
|
#
|
||||||
# Since: 2.9
|
# Since: 2.9
|
||||||
##
|
##
|
||||||
{ 'enum': 'BlockdevDriver',
|
{ 'enum': 'BlockdevDriver',
|
||||||
'data': [ 'blkdebug', 'blkverify', 'bochs', 'cloop',
|
'data': [ 'blkdebug', 'blkverify', 'bochs', 'cloop', 'copy-on-read',
|
||||||
'dmg', 'file', 'ftp', 'ftps', 'gluster', 'host_cdrom',
|
'dmg', 'file', 'ftp', 'ftps', 'gluster', 'host_cdrom',
|
||||||
'host_device', 'http', 'https', 'iscsi', 'luks', 'nbd', 'nfs',
|
'host_device', 'http', 'https', 'iscsi', 'luks', 'nbd', 'nfs',
|
||||||
'null-aio', 'null-co', 'nvme', 'parallels', 'qcow', 'qcow2', 'qed',
|
'null-aio', 'null-co', 'nvme', 'parallels', 'qcow', 'qcow2', 'qed',
|
||||||
@ -3527,6 +3532,7 @@
|
|||||||
'blkverify': 'BlockdevOptionsBlkverify',
|
'blkverify': 'BlockdevOptionsBlkverify',
|
||||||
'bochs': 'BlockdevOptionsGenericFormat',
|
'bochs': 'BlockdevOptionsGenericFormat',
|
||||||
'cloop': 'BlockdevOptionsGenericFormat',
|
'cloop': 'BlockdevOptionsGenericFormat',
|
||||||
|
'copy-on-read':'BlockdevOptionsGenericFormat',
|
||||||
'dmg': 'BlockdevOptionsGenericFormat',
|
'dmg': 'BlockdevOptionsGenericFormat',
|
||||||
'file': 'BlockdevOptionsFile',
|
'file': 'BlockdevOptionsFile',
|
||||||
'ftp': 'BlockdevOptionsCurlFtp',
|
'ftp': 'BlockdevOptionsCurlFtp',
|
||||||
@ -4054,6 +4060,7 @@
|
|||||||
'blkverify': 'BlockdevCreateNotSupported',
|
'blkverify': 'BlockdevCreateNotSupported',
|
||||||
'bochs': 'BlockdevCreateNotSupported',
|
'bochs': 'BlockdevCreateNotSupported',
|
||||||
'cloop': 'BlockdevCreateNotSupported',
|
'cloop': 'BlockdevCreateNotSupported',
|
||||||
|
'copy-on-read': 'BlockdevCreateNotSupported',
|
||||||
'dmg': 'BlockdevCreateNotSupported',
|
'dmg': 'BlockdevCreateNotSupported',
|
||||||
'file': 'BlockdevCreateOptionsFile',
|
'file': 'BlockdevCreateOptionsFile',
|
||||||
'ftp': 'BlockdevCreateNotSupported',
|
'ftp': 'BlockdevCreateNotSupported',
|
||||||
|
43
qemu-img.c
43
qemu-img.c
@ -277,12 +277,12 @@ static BlockBackend *img_open_opts(const char *optstr,
|
|||||||
options = qemu_opts_to_qdict(opts, NULL);
|
options = qemu_opts_to_qdict(opts, NULL);
|
||||||
if (force_share) {
|
if (force_share) {
|
||||||
if (qdict_haskey(options, BDRV_OPT_FORCE_SHARE)
|
if (qdict_haskey(options, BDRV_OPT_FORCE_SHARE)
|
||||||
&& !qdict_get_bool(options, BDRV_OPT_FORCE_SHARE)) {
|
&& strcmp(qdict_get_str(options, BDRV_OPT_FORCE_SHARE), "on")) {
|
||||||
error_report("--force-share/-U conflicts with image options");
|
error_report("--force-share/-U conflicts with image options");
|
||||||
qobject_unref(options);
|
qobject_unref(options);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
qdict_put_bool(options, BDRV_OPT_FORCE_SHARE, true);
|
qdict_put_str(options, BDRV_OPT_FORCE_SHARE, "on");
|
||||||
}
|
}
|
||||||
blk = blk_new_open(NULL, NULL, options, flags, &local_err);
|
blk = blk_new_open(NULL, NULL, options, flags, &local_err);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
@ -3381,7 +3381,7 @@ static int img_resize(int argc, char **argv)
|
|||||||
Error *err = NULL;
|
Error *err = NULL;
|
||||||
int c, ret, relative;
|
int c, ret, relative;
|
||||||
const char *filename, *fmt, *size;
|
const char *filename, *fmt, *size;
|
||||||
int64_t n, total_size, current_size;
|
int64_t n, total_size, current_size, new_size;
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
BlockBackend *blk = NULL;
|
BlockBackend *blk = NULL;
|
||||||
PreallocMode prealloc = PREALLOC_MODE_OFF;
|
PreallocMode prealloc = PREALLOC_MODE_OFF;
|
||||||
@ -3557,11 +3557,42 @@ static int img_resize(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = blk_truncate(blk, total_size, prealloc, &err);
|
ret = blk_truncate(blk, total_size, prealloc, &err);
|
||||||
if (!ret) {
|
if (ret < 0) {
|
||||||
qprintf(quiet, "Image resized.\n");
|
|
||||||
} else {
|
|
||||||
error_report_err(err);
|
error_report_err(err);
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new_size = blk_getlength(blk);
|
||||||
|
if (new_size < 0) {
|
||||||
|
error_report("Failed to verify truncated image length: %s",
|
||||||
|
strerror(-new_size));
|
||||||
|
ret = -1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Some block drivers implement a truncation method, but only so
|
||||||
|
* the user can cause qemu to refresh the image's size from disk.
|
||||||
|
* The idea is that the user resizes the image outside of qemu and
|
||||||
|
* then invokes block_resize to inform qemu about it.
|
||||||
|
* (This includes iscsi and file-posix for device files.)
|
||||||
|
* Of course, that is not the behavior someone invoking
|
||||||
|
* qemu-img resize would find useful, so we catch that behavior
|
||||||
|
* here and tell the user. */
|
||||||
|
if (new_size != total_size && new_size == current_size) {
|
||||||
|
error_report("Image was not resized; resizing may not be supported "
|
||||||
|
"for this image");
|
||||||
|
ret = -1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_size != total_size) {
|
||||||
|
warn_report("Image should have been resized to %" PRIi64
|
||||||
|
" bytes, but was resized to %" PRIi64 " bytes",
|
||||||
|
total_size, new_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
qprintf(quiet, "Image resized.\n");
|
||||||
|
|
||||||
out:
|
out:
|
||||||
blk_unref(blk);
|
blk_unref(blk);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -95,12 +95,12 @@ static int openfile(char *name, int flags, bool writethrough, bool force_share,
|
|||||||
opts = qdict_new();
|
opts = qdict_new();
|
||||||
}
|
}
|
||||||
if (qdict_haskey(opts, BDRV_OPT_FORCE_SHARE)
|
if (qdict_haskey(opts, BDRV_OPT_FORCE_SHARE)
|
||||||
&& !qdict_get_bool(opts, BDRV_OPT_FORCE_SHARE)) {
|
&& strcmp(qdict_get_str(opts, BDRV_OPT_FORCE_SHARE), "on")) {
|
||||||
error_report("-U conflicts with image options");
|
error_report("-U conflicts with image options");
|
||||||
qobject_unref(opts);
|
qobject_unref(opts);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
qdict_put_bool(opts, BDRV_OPT_FORCE_SHARE, true);
|
qdict_put_str(opts, BDRV_OPT_FORCE_SHARE, "on");
|
||||||
}
|
}
|
||||||
qemuio_blk = blk_new_open(name, NULL, opts, flags, &local_err);
|
qemuio_blk = blk_new_open(name, NULL, opts, flags, &local_err);
|
||||||
if (!qemuio_blk) {
|
if (!qemuio_blk) {
|
||||||
|
@ -129,53 +129,6 @@ $QEMU_IO -c "read -P 0x44 1023k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _fil
|
|||||||
$QEMU_IO -c "read -P 0 1024k 1022k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -c "read -P 0 1024k 1022k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "=== Corrupted size field in compressed cluster descriptor ==="
|
|
||||||
echo
|
|
||||||
# Create an empty image and fill half of it with compressed data.
|
|
||||||
# The L2 entries of the two compressed clusters are located at
|
|
||||||
# 0x800000 and 0x800008, their original values are 0x4008000000a00000
|
|
||||||
# and 0x4008000000a00802 (5 sectors for compressed data each).
|
|
||||||
_make_test_img 8M -o cluster_size=2M
|
|
||||||
$QEMU_IO -c "write -c -P 0x11 0 2M" -c "write -c -P 0x11 2M 2M" "$TEST_IMG" \
|
|
||||||
2>&1 | _filter_qemu_io | _filter_testdir
|
|
||||||
|
|
||||||
# Reduce size of compressed data to 4 sectors: this corrupts the image.
|
|
||||||
poke_file "$TEST_IMG" $((0x800000)) "\x40\x06"
|
|
||||||
$QEMU_IO -c "read -P 0x11 0 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
|
||||||
|
|
||||||
# 'qemu-img check' however doesn't see anything wrong because it
|
|
||||||
# doesn't try to decompress the data and the refcounts are consistent.
|
|
||||||
# TODO: update qemu-img so this can be detected.
|
|
||||||
_check_test_img
|
|
||||||
|
|
||||||
# Increase size of compressed data to the maximum (8192 sectors).
|
|
||||||
# This makes QEMU read more data (8192 sectors instead of 5, host
|
|
||||||
# addresses [0xa00000, 0xdfffff]), but the decompression algorithm
|
|
||||||
# stops once we have enough to restore the uncompressed cluster, so
|
|
||||||
# the rest of the data is ignored.
|
|
||||||
poke_file "$TEST_IMG" $((0x800000)) "\x7f\xfe"
|
|
||||||
# Do it also for the second compressed cluster (L2 entry at 0x800008).
|
|
||||||
# In this case the compressed data would span 3 host clusters
|
|
||||||
# (host addresses: [0xa00802, 0xe00801])
|
|
||||||
poke_file "$TEST_IMG" $((0x800008)) "\x7f\xfe"
|
|
||||||
|
|
||||||
# Here the image is too small so we're asking QEMU to read beyond the
|
|
||||||
# end of the image.
|
|
||||||
$QEMU_IO -c "read -P 0x11 0 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
|
||||||
# But if we grow the image we won't be reading beyond its end anymore.
|
|
||||||
$QEMU_IO -c "write -P 0x22 4M 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
|
||||||
$QEMU_IO -c "read -P 0x11 0 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
|
||||||
|
|
||||||
# The refcount data is however wrong because due to the increased size
|
|
||||||
# of the compressed data it now reaches the following host clusters.
|
|
||||||
# This can be repaired by qemu-img check by increasing the refcount of
|
|
||||||
# those clusters.
|
|
||||||
# TODO: update qemu-img to correct the compressed cluster size instead.
|
|
||||||
_check_test_img -r all
|
|
||||||
$QEMU_IO -c "read -P 0x11 0 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
|
||||||
$QEMU_IO -c "read -P 0x22 4M 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=== Full allocation with -S 0 ==="
|
echo "=== Full allocation with -S 0 ==="
|
||||||
echo
|
echo
|
||||||
|
@ -99,39 +99,6 @@ read 1024/1024 bytes at offset 1047552
|
|||||||
read 1046528/1046528 bytes at offset 1048576
|
read 1046528/1046528 bytes at offset 1048576
|
||||||
1022 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
1022 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
=== Corrupted size field in compressed cluster descriptor ===
|
|
||||||
|
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=8388608
|
|
||||||
wrote 2097152/2097152 bytes at offset 0
|
|
||||||
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
|
||||||
wrote 2097152/2097152 bytes at offset 2097152
|
|
||||||
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
|
||||||
read failed: Input/output error
|
|
||||||
No errors were found on the image.
|
|
||||||
read 4194304/4194304 bytes at offset 0
|
|
||||||
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
|
||||||
wrote 4194304/4194304 bytes at offset 4194304
|
|
||||||
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
|
||||||
read 4194304/4194304 bytes at offset 0
|
|
||||||
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
|
||||||
ERROR cluster 6 refcount=1 reference=3
|
|
||||||
ERROR cluster 7 refcount=1 reference=2
|
|
||||||
Repairing cluster 6 refcount=1 reference=3
|
|
||||||
Repairing cluster 7 refcount=1 reference=2
|
|
||||||
Repairing OFLAG_COPIED data cluster: l2_entry=8000000000c00000 refcount=3
|
|
||||||
Repairing OFLAG_COPIED data cluster: l2_entry=8000000000e00000 refcount=2
|
|
||||||
The following inconsistencies were found and repaired:
|
|
||||||
|
|
||||||
0 leaked clusters
|
|
||||||
4 corruptions
|
|
||||||
|
|
||||||
Double checking the fixed image now...
|
|
||||||
No errors were found on the image.
|
|
||||||
read 4194304/4194304 bytes at offset 0
|
|
||||||
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
|
||||||
read 4194304/4194304 bytes at offset 4194304
|
|
||||||
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
|
||||||
|
|
||||||
=== Full allocation with -S 0 ===
|
=== Full allocation with -S 0 ===
|
||||||
|
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
@ -22,7 +22,7 @@ refcount-cache-size may not exceed cache-size
|
|||||||
L2 cache size too big
|
L2 cache size too big
|
||||||
L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
||||||
L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
||||||
L2 cache size too big
|
Refcount cache size too big
|
||||||
Conflicting values for qcow2 options 'overlap-check' ('constant') and 'overlap-check.template' ('all')
|
Conflicting values for qcow2 options 'overlap-check' ('constant') and 'overlap-check.template' ('all')
|
||||||
Unsupported value 'blubb' for qcow2 option 'overlap-check'. Allowed are any of the following: none, constant, cached, all
|
Unsupported value 'blubb' for qcow2 option 'overlap-check'. Allowed are any of the following: none, constant, cached, all
|
||||||
Unsupported value 'blubb' for qcow2 option 'overlap-check'. Allowed are any of the following: none, constant, cached, all
|
Unsupported value 'blubb' for qcow2 option 'overlap-check'. Allowed are any of the following: none, constant, cached, all
|
||||||
|
@ -242,6 +242,23 @@ _run_cmd $QEMU_IO "${TEST_IMG}" -c 'write 0 512'
|
|||||||
|
|
||||||
_cleanup_qemu
|
_cleanup_qemu
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== Detecting -U and force-share conflicts =="
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo 'No conflict:'
|
||||||
|
$QEMU_IMG info -U --image-opts driver=null-co,force-share=on
|
||||||
|
echo
|
||||||
|
echo 'Conflict:'
|
||||||
|
$QEMU_IMG info -U --image-opts driver=null-co,force-share=off
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo 'No conflict:'
|
||||||
|
$QEMU_IO -c 'open -r -U -o driver=null-co,force-share=on'
|
||||||
|
echo
|
||||||
|
echo 'Conflict:'
|
||||||
|
$QEMU_IO -c 'open -r -U -o driver=null-co,force-share=off'
|
||||||
|
|
||||||
# success, all done
|
# success, all done
|
||||||
echo "*** done"
|
echo "*** done"
|
||||||
rm -f $seq.full
|
rm -f $seq.full
|
||||||
|
@ -399,4 +399,20 @@ Is another process using the image?
|
|||||||
Closing the other
|
Closing the other
|
||||||
|
|
||||||
_qemu_io_wrapper TEST_DIR/t.qcow2 -c write 0 512
|
_qemu_io_wrapper TEST_DIR/t.qcow2 -c write 0 512
|
||||||
|
|
||||||
|
== Detecting -U and force-share conflicts ==
|
||||||
|
|
||||||
|
No conflict:
|
||||||
|
image: null-co://
|
||||||
|
file format: null-co
|
||||||
|
virtual size: 1.0G (1073741824 bytes)
|
||||||
|
disk size: unavailable
|
||||||
|
|
||||||
|
Conflict:
|
||||||
|
qemu-img: --force-share/-U conflicts with image options
|
||||||
|
|
||||||
|
No conflict:
|
||||||
|
|
||||||
|
Conflict:
|
||||||
|
-U conflicts with image options
|
||||||
*** done
|
*** done
|
||||||
|
@ -96,6 +96,19 @@ echo
|
|||||||
# Enable postcopy-ram capability both on source and destination
|
# Enable postcopy-ram capability both on source and destination
|
||||||
silent=yes
|
silent=yes
|
||||||
_send_qemu_cmd $dest 'migrate_set_capability postcopy-ram on' "(qemu)"
|
_send_qemu_cmd $dest 'migrate_set_capability postcopy-ram on' "(qemu)"
|
||||||
|
|
||||||
|
qemu_error_no_exit=yes success_or_failure=yes \
|
||||||
|
_send_qemu_cmd $dest '' "(qemu)" "Postcopy is not supported"
|
||||||
|
if [ ${QEMU_STATUS[$dest]} -lt 0 ]; then
|
||||||
|
_send_qemu_cmd $dest '' "(qemu)"
|
||||||
|
|
||||||
|
_send_qemu_cmd $src 'quit' ""
|
||||||
|
_send_qemu_cmd $dest 'quit' ""
|
||||||
|
wait=1 _cleanup_qemu
|
||||||
|
|
||||||
|
_notrun 'Postcopy is not supported'
|
||||||
|
fi
|
||||||
|
|
||||||
_send_qemu_cmd $src 'migrate_set_speed 4k' "(qemu)"
|
_send_qemu_cmd $src 'migrate_set_speed 4k' "(qemu)"
|
||||||
_send_qemu_cmd $src 'migrate_set_capability postcopy-ram on' "(qemu)"
|
_send_qemu_cmd $src 'migrate_set_capability postcopy-ram on' "(qemu)"
|
||||||
_send_qemu_cmd $src "migrate -d unix:${MIG_SOCKET}" "(qemu)"
|
_send_qemu_cmd $src "migrate -d unix:${MIG_SOCKET}" "(qemu)"
|
||||||
|
@ -44,6 +44,7 @@ esac
|
|||||||
_cleanup()
|
_cleanup()
|
||||||
{
|
{
|
||||||
_cleanup_test_img
|
_cleanup_test_img
|
||||||
|
rm -f "$TEST_WRAP"
|
||||||
rm -f "$BLKDBG_CONF"
|
rm -f "$BLKDBG_CONF"
|
||||||
}
|
}
|
||||||
trap "_cleanup; exit \$status" 0 1 2 3 15
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||||
|
@ -82,6 +82,19 @@ echo
|
|||||||
|
|
||||||
silent=yes
|
silent=yes
|
||||||
_send_qemu_cmd $dest 'migrate_set_capability postcopy-ram on' "(qemu)"
|
_send_qemu_cmd $dest 'migrate_set_capability postcopy-ram on' "(qemu)"
|
||||||
|
|
||||||
|
qemu_error_no_exit=yes success_or_failure=yes \
|
||||||
|
_send_qemu_cmd $dest '' "(qemu)" "Postcopy is not supported"
|
||||||
|
if [ ${QEMU_STATUS[$dest]} -lt 0 ]; then
|
||||||
|
_send_qemu_cmd $dest '' "(qemu)"
|
||||||
|
|
||||||
|
_send_qemu_cmd $src 'quit' ""
|
||||||
|
_send_qemu_cmd $dest 'quit' ""
|
||||||
|
wait=1 _cleanup_qemu
|
||||||
|
|
||||||
|
_notrun 'Postcopy is not supported'
|
||||||
|
fi
|
||||||
|
|
||||||
_send_qemu_cmd $src 'migrate_set_capability postcopy-ram on' "(qemu)"
|
_send_qemu_cmd $src 'migrate_set_capability postcopy-ram on' "(qemu)"
|
||||||
_send_qemu_cmd $src "migrate -d unix:${MIG_SOCKET}" "(qemu)"
|
_send_qemu_cmd $src "migrate -d unix:${MIG_SOCKET}" "(qemu)"
|
||||||
|
|
||||||
|
97
tests/qemu-iotests/214
Executable file
97
tests/qemu-iotests/214
Executable file
@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Test qcow2 image compression
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Igalia, S.L.
|
||||||
|
# Author: Alberto Garcia <berto@igalia.com>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
seq=$(basename "$0")
|
||||||
|
echo "QA output created by $seq"
|
||||||
|
|
||||||
|
here=$PWD
|
||||||
|
status=1 # failure is the default!
|
||||||
|
|
||||||
|
_cleanup()
|
||||||
|
{
|
||||||
|
_cleanup_test_img
|
||||||
|
}
|
||||||
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||||
|
|
||||||
|
# get standard environment, filters and checks
|
||||||
|
. ./common.rc
|
||||||
|
. ./common.filter
|
||||||
|
|
||||||
|
_supported_fmt qcow2
|
||||||
|
_supported_proto file
|
||||||
|
_supported_os Linux
|
||||||
|
|
||||||
|
# Repairing the corrupted image requires qemu-img check to store a
|
||||||
|
# refcount up to 3, which requires at least two refcount bits.
|
||||||
|
_unsupported_imgopts 'refcount_bits=1[^0-9]'
|
||||||
|
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Corrupted size field in compressed cluster descriptor ==="
|
||||||
|
echo
|
||||||
|
# Create an empty image and fill half of it with compressed data.
|
||||||
|
# The L2 entries of the two compressed clusters are located at
|
||||||
|
# 0x800000 and 0x800008, their original values are 0x4008000000a00000
|
||||||
|
# and 0x4008000000a00802 (5 sectors for compressed data each).
|
||||||
|
_make_test_img 8M -o cluster_size=2M
|
||||||
|
$QEMU_IO -c "write -c -P 0x11 0 2M" -c "write -c -P 0x11 2M 2M" "$TEST_IMG" \
|
||||||
|
2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
# Reduce size of compressed data to 4 sectors: this corrupts the image.
|
||||||
|
poke_file "$TEST_IMG" $((0x800000)) "\x40\x06"
|
||||||
|
$QEMU_IO -c "read -P 0x11 0 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
# 'qemu-img check' however doesn't see anything wrong because it
|
||||||
|
# doesn't try to decompress the data and the refcounts are consistent.
|
||||||
|
# TODO: update qemu-img so this can be detected.
|
||||||
|
_check_test_img
|
||||||
|
|
||||||
|
# Increase size of compressed data to the maximum (8192 sectors).
|
||||||
|
# This makes QEMU read more data (8192 sectors instead of 5, host
|
||||||
|
# addresses [0xa00000, 0xdfffff]), but the decompression algorithm
|
||||||
|
# stops once we have enough to restore the uncompressed cluster, so
|
||||||
|
# the rest of the data is ignored.
|
||||||
|
poke_file "$TEST_IMG" $((0x800000)) "\x7f\xfe"
|
||||||
|
# Do it also for the second compressed cluster (L2 entry at 0x800008).
|
||||||
|
# In this case the compressed data would span 3 host clusters
|
||||||
|
# (host addresses: [0xa00802, 0xe00801])
|
||||||
|
poke_file "$TEST_IMG" $((0x800008)) "\x7f\xfe"
|
||||||
|
|
||||||
|
# Here the image is too small so we're asking QEMU to read beyond the
|
||||||
|
# end of the image.
|
||||||
|
$QEMU_IO -c "read -P 0x11 0 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
# But if we grow the image we won't be reading beyond its end anymore.
|
||||||
|
$QEMU_IO -c "write -P 0x22 4M 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x11 0 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
# The refcount data is however wrong because due to the increased size
|
||||||
|
# of the compressed data it now reaches the following host clusters.
|
||||||
|
# This can be repaired by qemu-img check by increasing the refcount of
|
||||||
|
# those clusters.
|
||||||
|
# TODO: update qemu-img to correct the compressed cluster size instead.
|
||||||
|
_check_test_img -r all
|
||||||
|
$QEMU_IO -c "read -P 0x11 0 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x22 4M 4M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
# success, all done
|
||||||
|
echo '*** done'
|
||||||
|
rm -f $seq.full
|
||||||
|
status=0
|
35
tests/qemu-iotests/214.out
Normal file
35
tests/qemu-iotests/214.out
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
QA output created by 214
|
||||||
|
|
||||||
|
=== Corrupted size field in compressed cluster descriptor ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=8388608
|
||||||
|
wrote 2097152/2097152 bytes at offset 0
|
||||||
|
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 2097152/2097152 bytes at offset 2097152
|
||||||
|
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read failed: Input/output error
|
||||||
|
No errors were found on the image.
|
||||||
|
read 4194304/4194304 bytes at offset 0
|
||||||
|
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 4194304/4194304 bytes at offset 4194304
|
||||||
|
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 4194304/4194304 bytes at offset 0
|
||||||
|
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
ERROR cluster 6 refcount=1 reference=3
|
||||||
|
ERROR cluster 7 refcount=1 reference=2
|
||||||
|
Repairing cluster 6 refcount=1 reference=3
|
||||||
|
Repairing cluster 7 refcount=1 reference=2
|
||||||
|
Repairing OFLAG_COPIED data cluster: l2_entry=8000000000c00000 refcount=3
|
||||||
|
Repairing OFLAG_COPIED data cluster: l2_entry=8000000000e00000 refcount=2
|
||||||
|
The following inconsistencies were found and repaired:
|
||||||
|
|
||||||
|
0 leaked clusters
|
||||||
|
4 corruptions
|
||||||
|
|
||||||
|
Double checking the fixed image now...
|
||||||
|
No errors were found on the image.
|
||||||
|
read 4194304/4194304 bytes at offset 0
|
||||||
|
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 4194304/4194304 bytes at offset 4194304
|
||||||
|
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
*** done
|
120
tests/qemu-iotests/215
Executable file
120
tests/qemu-iotests/215
Executable file
@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Test case for copy-on-read into qcow2, using the COR filter driver
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
seq="$(basename $0)"
|
||||||
|
echo "QA output created by $seq"
|
||||||
|
|
||||||
|
here="$PWD"
|
||||||
|
status=1 # failure is the default!
|
||||||
|
|
||||||
|
# get standard environment, filters and checks
|
||||||
|
. ./common.rc
|
||||||
|
. ./common.filter
|
||||||
|
|
||||||
|
TEST_WRAP="$TEST_DIR/t.wrap.qcow2"
|
||||||
|
BLKDBG_CONF="$TEST_DIR/blkdebug.conf"
|
||||||
|
|
||||||
|
# Sanity check: our use of blkdebug fails if $TEST_DIR contains spaces
|
||||||
|
# or other problems
|
||||||
|
case "$TEST_DIR" in
|
||||||
|
*[^-_a-zA-Z0-9/]*)
|
||||||
|
_notrun "Suspicious TEST_DIR='$TEST_DIR', cowardly refusing to run" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
_cleanup()
|
||||||
|
{
|
||||||
|
_cleanup_test_img
|
||||||
|
rm -f "$TEST_WRAP"
|
||||||
|
rm -f "$BLKDBG_CONF"
|
||||||
|
}
|
||||||
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||||
|
|
||||||
|
# Test is supported for any backing file; but we force qcow2 for our wrapper.
|
||||||
|
_supported_fmt generic
|
||||||
|
_supported_proto generic
|
||||||
|
_supported_os Linux
|
||||||
|
# LUKS support may be possible, but it complicates things.
|
||||||
|
_unsupported_fmt luks
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo '=== Copy-on-read ==='
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Prep the images
|
||||||
|
# VPC rounds image sizes to a specific geometry, force a specific size.
|
||||||
|
if [ "$IMGFMT" = "vpc" ]; then
|
||||||
|
IMGOPTS=$(_optstr_add "$IMGOPTS" "force_size")
|
||||||
|
fi
|
||||||
|
_make_test_img 4G
|
||||||
|
$QEMU_IO -c "write -P 55 3G 1k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
IMGPROTO=file IMGFMT=qcow2 IMGOPTS= TEST_IMG_FILE="$TEST_WRAP" \
|
||||||
|
_make_test_img -F "$IMGFMT" -b "$TEST_IMG" | _filter_img_create
|
||||||
|
$QEMU_IO -f qcow2 -c "write -z -u 1M 64k" "$TEST_WRAP" | _filter_qemu_io
|
||||||
|
|
||||||
|
# Ensure that a read of two clusters, but where one is already allocated,
|
||||||
|
# does not re-write the allocated cluster
|
||||||
|
cat > "$BLKDBG_CONF" <<EOF
|
||||||
|
[inject-error]
|
||||||
|
event = "cor_write"
|
||||||
|
sector = "2048"
|
||||||
|
EOF
|
||||||
|
$QEMU_IO -c "open \
|
||||||
|
-o driver=copy-on-read,file.driver=blkdebug,file.config=$BLKDBG_CONF,file.image.driver=qcow2 $TEST_WRAP" \
|
||||||
|
-c "read -P 0 1M 128k" | _filter_qemu_io
|
||||||
|
|
||||||
|
# Read the areas we want copied. A zero-length read should still be a
|
||||||
|
# no-op. The next read is under 2G, but aligned so that rounding to
|
||||||
|
# clusters copies more than 2G of zeroes. The final read will pick up
|
||||||
|
# the non-zero data in the same cluster. Since a 2G read may exhaust
|
||||||
|
# memory on some machines (particularly 32-bit), we skip the test if
|
||||||
|
# that fails due to memory pressure.
|
||||||
|
$QEMU_IO \
|
||||||
|
-c "open -o driver=copy-on-read,file.driver=qcow2 $TEST_WRAP" \
|
||||||
|
-c "read 0 0" \
|
||||||
|
| _filter_qemu_io
|
||||||
|
output=$($QEMU_IO \
|
||||||
|
-c "open -o driver=copy-on-read,file.driver=qcow2 $TEST_WRAP" \
|
||||||
|
-c "read -P 0 1k $((2*1024*1024*1024 - 512))" \
|
||||||
|
2>&1 | _filter_qemu_io)
|
||||||
|
case $output in
|
||||||
|
*allocate*)
|
||||||
|
_notrun "Insufficent memory to run test" ;;
|
||||||
|
*) printf '%s\n' "$output" ;;
|
||||||
|
esac
|
||||||
|
$QEMU_IO \
|
||||||
|
-c "open -o driver=copy-on-read,file.driver=qcow2 $TEST_WRAP" \
|
||||||
|
-c "read -P 0 $((3*1024*1024*1024 + 1024)) 1k" \
|
||||||
|
| _filter_qemu_io
|
||||||
|
|
||||||
|
# Copy-on-read is incompatible with read-only
|
||||||
|
$QEMU_IO \
|
||||||
|
-c "open -r -o driver=copy-on-read,file.driver=qcow2 $TEST_WRAP" \
|
||||||
|
2>&1 | _filter_testdir
|
||||||
|
|
||||||
|
# Break the backing chain, and show that images are identical, and that
|
||||||
|
# we properly copied over explicit zeros.
|
||||||
|
$QEMU_IMG rebase -u -b "" -f qcow2 "$TEST_WRAP"
|
||||||
|
$QEMU_IO -f qcow2 -c map "$TEST_WRAP"
|
||||||
|
_check_test_img
|
||||||
|
$QEMU_IMG compare -f $IMGFMT -F qcow2 "$TEST_IMG" "$TEST_WRAP"
|
||||||
|
|
||||||
|
# success, all done
|
||||||
|
echo '*** done'
|
||||||
|
status=0
|
26
tests/qemu-iotests/215.out
Normal file
26
tests/qemu-iotests/215.out
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
QA output created by 215
|
||||||
|
|
||||||
|
=== Copy-on-read ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296
|
||||||
|
wrote 1024/1024 bytes at offset 3221225472
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
Formatting 'TEST_DIR/t.wrap.IMGFMT', fmt=IMGFMT size=4294967296 backing_file=TEST_DIR/t.IMGFMT backing_fmt=IMGFMT
|
||||||
|
wrote 65536/65536 bytes at offset 1048576
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 131072/131072 bytes at offset 1048576
|
||||||
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 0/0 bytes at offset 0
|
||||||
|
0 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 2147483136/2147483136 bytes at offset 1024
|
||||||
|
2 GiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 1024/1024 bytes at offset 3221226496
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
can't open device TEST_DIR/t.wrap.qcow2: Block node is read-only
|
||||||
|
2 GiB (0x80010000) bytes allocated at offset 0 bytes (0x0)
|
||||||
|
1023.938 MiB (0x3fff0000) bytes not allocated at offset 2 GiB (0x80010000)
|
||||||
|
64 KiB (0x10000) bytes allocated at offset 3 GiB (0xc0000000)
|
||||||
|
1023.938 MiB (0x3fff0000) bytes not allocated at offset 3 GiB (0xc0010000)
|
||||||
|
No errors were found on the image.
|
||||||
|
Images are identical.
|
||||||
|
*** done
|
115
tests/qemu-iotests/216
Executable file
115
tests/qemu-iotests/216
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copy-on-read tests using a COR filter node
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# Creator/Owner: Max Reitz <mreitz@redhat.com>
|
||||||
|
|
||||||
|
import iotests
|
||||||
|
from iotests import log, qemu_img_pipe, qemu_io, filter_qemu_io
|
||||||
|
|
||||||
|
# Need backing file support
|
||||||
|
iotests.verify_image_format(supported_fmts=['qcow2', 'qcow', 'qed', 'vmdk'])
|
||||||
|
iotests.verify_platform(['linux'])
|
||||||
|
|
||||||
|
log('')
|
||||||
|
log('=== Copy-on-read across nodes ===')
|
||||||
|
log('')
|
||||||
|
|
||||||
|
# The old copy-on-read mechanism without a filter node cannot request
|
||||||
|
# WRITE_UNCHANGED permissions for its child. Therefore it just tries
|
||||||
|
# to sneak its write by the usual permission system and holds its
|
||||||
|
# fingers crossed. However, that sneaking does not work so well when
|
||||||
|
# there is a filter node in the way: That will receive the write
|
||||||
|
# request and re-issue a new one to its child, which this time is a
|
||||||
|
# proper write request that will make the permission system cough --
|
||||||
|
# unless there is someone at the top (like a guest device) that has
|
||||||
|
# requested write permissions.
|
||||||
|
#
|
||||||
|
# A COR filter node, however, can request the proper permissions for
|
||||||
|
# its child and therefore is not hit by this issue.
|
||||||
|
|
||||||
|
with iotests.FilePath('base.img') as base_img_path, \
|
||||||
|
iotests.FilePath('top.img') as top_img_path, \
|
||||||
|
iotests.VM() as vm:
|
||||||
|
|
||||||
|
log('--- Setting up images ---')
|
||||||
|
log('')
|
||||||
|
|
||||||
|
qemu_img_pipe('create', '-f', iotests.imgfmt, base_img_path, '64M')
|
||||||
|
|
||||||
|
log(filter_qemu_io(qemu_io(base_img_path, '-c', 'write -P 1 0M 1M')))
|
||||||
|
|
||||||
|
qemu_img_pipe('create', '-f', iotests.imgfmt, '-b', base_img_path,
|
||||||
|
top_img_path)
|
||||||
|
|
||||||
|
log(filter_qemu_io(qemu_io(top_img_path, '-c', 'write -P 2 1M 1M')))
|
||||||
|
|
||||||
|
log('')
|
||||||
|
log('--- Doing COR ---')
|
||||||
|
log('')
|
||||||
|
|
||||||
|
# Compare with e.g. the following:
|
||||||
|
# vm.add_drive_raw('if=none,node-name=node0,copy-on-read=on,driver=raw,' \
|
||||||
|
# 'file.driver=%s,file.file.filename=%s' %
|
||||||
|
# (iotests.imgfmt, top_img_path))
|
||||||
|
# (Remove the blockdev-add instead.)
|
||||||
|
# ((Not tested here because it hits an assertion in the permission
|
||||||
|
# system.))
|
||||||
|
|
||||||
|
vm.launch()
|
||||||
|
|
||||||
|
log(vm.qmp('blockdev-add',
|
||||||
|
node_name='node0',
|
||||||
|
driver='copy-on-read',
|
||||||
|
file={
|
||||||
|
'driver': 'raw',
|
||||||
|
'file': {
|
||||||
|
'driver': 'copy-on-read',
|
||||||
|
'file': {
|
||||||
|
'driver': 'raw',
|
||||||
|
'file': {
|
||||||
|
'driver': iotests.imgfmt,
|
||||||
|
'file': {
|
||||||
|
'driver': 'file',
|
||||||
|
'filename': top_img_path
|
||||||
|
},
|
||||||
|
'backing': {
|
||||||
|
'driver': iotests.imgfmt,
|
||||||
|
'file': {
|
||||||
|
'driver': 'file',
|
||||||
|
'filename': base_img_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Trigger COR
|
||||||
|
log(vm.qmp('human-monitor-command',
|
||||||
|
command_line='qemu-io node0 "read 0 64M"'))
|
||||||
|
|
||||||
|
vm.shutdown()
|
||||||
|
|
||||||
|
log('')
|
||||||
|
log('--- Checking COR result ---')
|
||||||
|
log('')
|
||||||
|
|
||||||
|
log(filter_qemu_io(qemu_io(base_img_path, '-c', 'discard 0 64M')))
|
||||||
|
log(filter_qemu_io(qemu_io(top_img_path, '-c', 'read -P 1 0M 1M')))
|
||||||
|
log(filter_qemu_io(qemu_io(top_img_path, '-c', 'read -P 2 1M 1M')))
|
28
tests/qemu-iotests/216.out
Normal file
28
tests/qemu-iotests/216.out
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
=== Copy-on-read across nodes ===
|
||||||
|
|
||||||
|
--- Setting up images ---
|
||||||
|
|
||||||
|
wrote 1048576/1048576 bytes at offset 0
|
||||||
|
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
wrote 1048576/1048576 bytes at offset 1048576
|
||||||
|
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
|
||||||
|
--- Doing COR ---
|
||||||
|
|
||||||
|
{u'return': {}}
|
||||||
|
{u'return': u''}
|
||||||
|
|
||||||
|
--- Checking COR result ---
|
||||||
|
|
||||||
|
discard 67108864/67108864 bytes at offset 0
|
||||||
|
64 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
read 1048576/1048576 bytes at offset 0
|
||||||
|
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
read 1048576/1048576 bytes at offset 1048576
|
||||||
|
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
@ -52,11 +52,29 @@ _in_fd=4
|
|||||||
# response is not echoed out.
|
# response is not echoed out.
|
||||||
# If $mismatch_only is set, only non-matching responses will
|
# If $mismatch_only is set, only non-matching responses will
|
||||||
# be echoed.
|
# be echoed.
|
||||||
|
#
|
||||||
|
# If $success_or_failure is set, the meaning of the arguments is
|
||||||
|
# changed as follows:
|
||||||
|
# $2: A string to search for in the response; if found, this indicates
|
||||||
|
# success and ${QEMU_STATUS[$1]} is set to 0.
|
||||||
|
# $3: A string to search for in the response; if found, this indicates
|
||||||
|
# failure and the test is either aborted (if $qemu_error_no_exit
|
||||||
|
# is not set) or ${QEMU_STATUS[$1]} is set to -1 (otherwise).
|
||||||
function _timed_wait_for()
|
function _timed_wait_for()
|
||||||
{
|
{
|
||||||
local h=${1}
|
local h=${1}
|
||||||
shift
|
shift
|
||||||
|
|
||||||
|
if [ -z "${success_or_failure}" ]; then
|
||||||
|
success_match=${*}
|
||||||
|
failure_match=
|
||||||
|
else
|
||||||
|
success_match=${1}
|
||||||
|
failure_match=${2}
|
||||||
|
fi
|
||||||
|
|
||||||
|
timeout=yes
|
||||||
|
|
||||||
QEMU_STATUS[$h]=0
|
QEMU_STATUS[$h]=0
|
||||||
while IFS= read -t ${QEMU_COMM_TIMEOUT} resp <&${QEMU_OUT[$h]}
|
while IFS= read -t ${QEMU_COMM_TIMEOUT} resp <&${QEMU_OUT[$h]}
|
||||||
do
|
do
|
||||||
@ -64,10 +82,18 @@ function _timed_wait_for()
|
|||||||
echo "${resp}" | _filter_testdir | _filter_qemu \
|
echo "${resp}" | _filter_testdir | _filter_qemu \
|
||||||
| _filter_qemu_io | _filter_qmp | _filter_hmp
|
| _filter_qemu_io | _filter_qmp | _filter_hmp
|
||||||
fi
|
fi
|
||||||
grep -q "${*}" < <(echo "${resp}")
|
if [ -n "${failure_match}" ]; then
|
||||||
|
grep -q "${failure_match}" < <(echo "${resp}")
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
timeout=
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
grep -q "${success_match}" < <(echo "${resp}")
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
return
|
return
|
||||||
elif [ -z "${silent}" ] && [ -n "${mismatch_only}" ]; then
|
fi
|
||||||
|
if [ -z "${silent}" ] && [ -n "${mismatch_only}" ]; then
|
||||||
echo "${resp}" | _filter_testdir | _filter_qemu \
|
echo "${resp}" | _filter_testdir | _filter_qemu \
|
||||||
| _filter_qemu_io | _filter_qmp | _filter_hmp
|
| _filter_qemu_io | _filter_qmp | _filter_hmp
|
||||||
fi
|
fi
|
||||||
@ -75,8 +101,12 @@ function _timed_wait_for()
|
|||||||
done
|
done
|
||||||
QEMU_STATUS[$h]=-1
|
QEMU_STATUS[$h]=-1
|
||||||
if [ -z "${qemu_error_no_exit}" ]; then
|
if [ -z "${qemu_error_no_exit}" ]; then
|
||||||
echo "Timeout waiting for ${*} on handle ${h}"
|
if [ -n "${timeout}" ]; then
|
||||||
exit 1 # Timeout means the test failed
|
echo "Timeout waiting for ${success_match} on handle ${h}"
|
||||||
|
else
|
||||||
|
echo "Wrong response matching ${failure_match} on handle ${h}"
|
||||||
|
fi
|
||||||
|
exit 1 # Timeout or wrong match mean the test failed
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +126,11 @@ function _timed_wait_for()
|
|||||||
# If $qemu_error_no_exit is set, then even if the expected response
|
# If $qemu_error_no_exit is set, then even if the expected response
|
||||||
# is not seen, we will not exit. $QEMU_STATUS[$1] will be set it -1 in
|
# is not seen, we will not exit. $QEMU_STATUS[$1] will be set it -1 in
|
||||||
# that case.
|
# that case.
|
||||||
|
#
|
||||||
|
# If $success_or_failure is set, then the last two strings are the
|
||||||
|
# strings the response will be scanned for. The first of the two
|
||||||
|
# indicates success, the latter indicates failure. Failure is handled
|
||||||
|
# like a timeout.
|
||||||
function _send_qemu_cmd()
|
function _send_qemu_cmd()
|
||||||
{
|
{
|
||||||
local h=${1}
|
local h=${1}
|
||||||
@ -109,14 +144,23 @@ function _send_qemu_cmd()
|
|||||||
use_error="no"
|
use_error="no"
|
||||||
fi
|
fi
|
||||||
# This array element extraction is done to accommodate pathnames with spaces
|
# This array element extraction is done to accommodate pathnames with spaces
|
||||||
|
if [ -z "${success_or_failure}" ]; then
|
||||||
cmd=${@: 1:${#@}-1}
|
cmd=${@: 1:${#@}-1}
|
||||||
shift $(($# - 1))
|
shift $(($# - 1))
|
||||||
|
else
|
||||||
|
cmd=${@: 1:${#@}-2}
|
||||||
|
shift $(($# - 2))
|
||||||
|
fi
|
||||||
|
|
||||||
while [ ${count} -gt 0 ]
|
while [ ${count} -gt 0 ]
|
||||||
do
|
do
|
||||||
echo "${cmd}" >&${QEMU_IN[${h}]}
|
echo "${cmd}" >&${QEMU_IN[${h}]}
|
||||||
if [ -n "${1}" ]; then
|
if [ -n "${1}" ]; then
|
||||||
|
if [ -z "${success_or_failure}" ]; then
|
||||||
qemu_error_no_exit=${use_error} _timed_wait_for ${h} "${1}"
|
qemu_error_no_exit=${use_error} _timed_wait_for ${h} "${1}"
|
||||||
|
else
|
||||||
|
qemu_error_no_exit=${use_error} _timed_wait_for ${h} "${1}" "${2}"
|
||||||
|
fi
|
||||||
if [ ${QEMU_STATUS[$h]} -eq 0 ]; then
|
if [ ${QEMU_STATUS[$h]} -eq 0 ]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
@ -212,4 +212,7 @@
|
|||||||
211 rw auto quick
|
211 rw auto quick
|
||||||
212 rw auto quick
|
212 rw auto quick
|
||||||
213 rw auto quick
|
213 rw auto quick
|
||||||
|
214 rw auto
|
||||||
|
215 rw auto quick
|
||||||
|
216 rw auto quick
|
||||||
218 rw auto quick
|
218 rw auto quick
|
||||||
|
Loading…
Reference in New Issue
Block a user