mirror of https://gitee.com/openkylin/qemu.git
Block layer patches
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJZUQcUAAoJEH8JsnLIjy/WIeMQAJQCJcUdV7g5Uiz11ila1zA1 rypcDIoEG6DriadqhK+3Ex4fVAR21u5kJLD7wXjfnh9ip+ssUhYO2rttvjsGuyNQ nSx77itq2Mdb9Eyrus8ff1L6vSK7nGiUJEQxd2qhgGs05coPDtG8pko+/ppGAp2E mcxcRUBC3gkMKNDr9vIfwI9GLtpeAramIOuPbV/gXVi0RFZSKEBBRaZbxNtHNibG i+ESoX5a+jitns6AmZv0WaE29Ye50HNLi2DZ2Im39/QrQDyOsFS0bsSO4VaDeBEW 2HjFvD83DaStxwukglE7nEz7ktZ/RxIyhf4cazNWcrWDJWm082jSXnWgurv2MxFd pNT4+pX0f7POKIKBdYm5a4TPrm9EpR0FHTfgf0hjygkKtSnaBiYOOtmctGDDXGOw aT1pxU/7OoRZ7SPGtaiUVgTImpypG0S5a2NL5vi0Rh40y1Jb82dry0qnDFRNoE1n wP6vd5te5wWEnVUwobsvgqcwCEdvBWALb/sxUZPdKw01AM9n4u/m8T4tX8tboSRa S2v8n0VaMzuPkZEKaHBHU4kh81fiQe3B7chGOvNTGetrMnX7d1kuv6RA3XcCopnW tqVRHQYzIg31gokIk+sY7nJEHcI2Mum9edXU9f0AQ7L9Vtt+MBNDW4DgxBGc9et/ DBTuBcNdHRJxELGsgTie =9I7S -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches # gpg: Signature made Mon 26 Jun 2017 14:07:32 BST # gpg: using RSA key 0x7F09B272C88F2FD6 # gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" # Primary key fingerprint: DC3D EB15 9A9A F95D 3D74 56FE 7F09 B272 C88F 2FD6 * remotes/kevin/tags/for-upstream: (60 commits) qemu-img: don't shadow opts variable in img_dd() block: Do not strcmp() with NULL uri->scheme blkverify: Catch bs->exact_filename overflow blkdebug: Catch bs->exact_filename overflow fix: avoid an infinite loop or a dangling pointer problem in img_commit block: change variable names in BlockDriverState block: Remove bdrv_aio_readv/writev/flush() qed: Use bdrv_co_* for coroutine_fns qed: Add coroutine_fn to I/O path functions qed: Use a coroutine for need_check_timer qed: Simplify request handling qed: Use CoQueue for serialising allocations qed: Implement .bdrv_co_readv/writev qed: Remove recursion in qed_aio_next_io() qed: Remove ret argument from qed_aio_next_io() qed: Add return value to qed_aio_read/write_data() qed: Add return value to qed_aio_write_inplace/alloc() qed: Add return value to qed_aio_write_cow() qed: Add return value to qed_aio_write_main() qed: Add return value to qed_aio_write_l2_update() ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
054914f646
|
@ -1,6 +1,6 @@
|
|||
block-obj-y += raw-format.o qcow.o vdi.o vmdk.o cloop.o bochs.o vpc.o vvfat.o dmg.o
|
||||
block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.o
|
||||
block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
|
||||
block-obj-y += qed.o qed-l2-cache.o qed-table.o qed-cluster.o
|
||||
block-obj-y += qed-check.o
|
||||
block-obj-y += vhdx.o vhdx-endian.o vhdx-log.o
|
||||
block-obj-y += quorum.o
|
||||
|
|
|
@ -575,7 +575,7 @@ static int blkdebug_co_flush(BlockDriverState *bs)
|
|||
}
|
||||
|
||||
static int coroutine_fn blkdebug_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int count,
|
||||
int64_t offset, int bytes,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
uint32_t align = MAX(bs->bl.request_alignment,
|
||||
|
@ -586,29 +586,29 @@ static int coroutine_fn blkdebug_co_pwrite_zeroes(BlockDriverState *bs,
|
|||
* preferred alignment (so that we test the fallback to writes on
|
||||
* unaligned portions), and check that the block layer never hands
|
||||
* us anything unaligned that crosses an alignment boundary. */
|
||||
if (count < align) {
|
||||
if (bytes < align) {
|
||||
assert(QEMU_IS_ALIGNED(offset, align) ||
|
||||
QEMU_IS_ALIGNED(offset + count, align) ||
|
||||
QEMU_IS_ALIGNED(offset + bytes, align) ||
|
||||
DIV_ROUND_UP(offset, align) ==
|
||||
DIV_ROUND_UP(offset + count, align));
|
||||
DIV_ROUND_UP(offset + bytes, align));
|
||||
return -ENOTSUP;
|
||||
}
|
||||
assert(QEMU_IS_ALIGNED(offset, align));
|
||||
assert(QEMU_IS_ALIGNED(count, align));
|
||||
assert(QEMU_IS_ALIGNED(bytes, align));
|
||||
if (bs->bl.max_pwrite_zeroes) {
|
||||
assert(count <= bs->bl.max_pwrite_zeroes);
|
||||
assert(bytes <= bs->bl.max_pwrite_zeroes);
|
||||
}
|
||||
|
||||
err = rule_check(bs, offset, count);
|
||||
err = rule_check(bs, offset, bytes);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return bdrv_co_pwrite_zeroes(bs->file, offset, count, flags);
|
||||
return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
|
||||
}
|
||||
|
||||
static int coroutine_fn blkdebug_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int count)
|
||||
int64_t offset, int bytes)
|
||||
{
|
||||
uint32_t align = bs->bl.pdiscard_alignment;
|
||||
int err;
|
||||
|
@ -616,29 +616,29 @@ static int coroutine_fn blkdebug_co_pdiscard(BlockDriverState *bs,
|
|||
/* Only pass through requests that are larger than requested
|
||||
* minimum alignment, and ensure that unaligned requests do not
|
||||
* cross optimum discard boundaries. */
|
||||
if (count < bs->bl.request_alignment) {
|
||||
if (bytes < bs->bl.request_alignment) {
|
||||
assert(QEMU_IS_ALIGNED(offset, align) ||
|
||||
QEMU_IS_ALIGNED(offset + count, align) ||
|
||||
QEMU_IS_ALIGNED(offset + bytes, align) ||
|
||||
DIV_ROUND_UP(offset, align) ==
|
||||
DIV_ROUND_UP(offset + count, align));
|
||||
DIV_ROUND_UP(offset + bytes, align));
|
||||
return -ENOTSUP;
|
||||
}
|
||||
assert(QEMU_IS_ALIGNED(offset, bs->bl.request_alignment));
|
||||
assert(QEMU_IS_ALIGNED(count, bs->bl.request_alignment));
|
||||
if (align && count >= align) {
|
||||
assert(QEMU_IS_ALIGNED(bytes, bs->bl.request_alignment));
|
||||
if (align && bytes >= align) {
|
||||
assert(QEMU_IS_ALIGNED(offset, align));
|
||||
assert(QEMU_IS_ALIGNED(count, align));
|
||||
assert(QEMU_IS_ALIGNED(bytes, align));
|
||||
}
|
||||
if (bs->bl.max_pdiscard) {
|
||||
assert(count <= bs->bl.max_pdiscard);
|
||||
assert(bytes <= bs->bl.max_pdiscard);
|
||||
}
|
||||
|
||||
err = rule_check(bs, offset, count);
|
||||
err = rule_check(bs, offset, bytes);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return bdrv_co_pdiscard(bs->file->bs, offset, count);
|
||||
return bdrv_co_pdiscard(bs->file->bs, offset, bytes);
|
||||
}
|
||||
|
||||
static void blkdebug_close(BlockDriverState *bs)
|
||||
|
@ -839,9 +839,13 @@ static void blkdebug_refresh_filename(BlockDriverState *bs, QDict *options)
|
|||
}
|
||||
|
||||
if (!force_json && bs->file->bs->exact_filename[0]) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
int ret = snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"blkdebug:%s:%s", s->config_file ?: "",
|
||||
bs->file->bs->exact_filename);
|
||||
if (ret >= sizeof(bs->exact_filename)) {
|
||||
/* An overflow makes the filename unusable, so do not report any */
|
||||
bs->exact_filename[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
opts = qdict_new();
|
||||
|
|
|
@ -96,10 +96,10 @@ static int coroutine_fn blkreplay_co_pwritev(BlockDriverState *bs,
|
|||
}
|
||||
|
||||
static int coroutine_fn blkreplay_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int count, BdrvRequestFlags flags)
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
uint64_t reqid = blkreplay_next_id();
|
||||
int ret = bdrv_co_pwrite_zeroes(bs->file, offset, count, flags);
|
||||
int ret = bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
|
||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
||||
qemu_coroutine_yield();
|
||||
|
||||
|
@ -107,10 +107,10 @@ static int coroutine_fn blkreplay_co_pwrite_zeroes(BlockDriverState *bs,
|
|||
}
|
||||
|
||||
static int coroutine_fn blkreplay_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int count)
|
||||
int64_t offset, int bytes)
|
||||
{
|
||||
uint64_t reqid = blkreplay_next_id();
|
||||
int ret = bdrv_co_pdiscard(bs->file->bs, offset, count);
|
||||
int ret = bdrv_co_pdiscard(bs->file->bs, offset, bytes);
|
||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
||||
qemu_coroutine_yield();
|
||||
|
||||
|
|
|
@ -301,10 +301,14 @@ static void blkverify_refresh_filename(BlockDriverState *bs, QDict *options)
|
|||
if (bs->file->bs->exact_filename[0]
|
||||
&& s->test_file->bs->exact_filename[0])
|
||||
{
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
int ret = snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"blkverify:%s:%s",
|
||||
bs->file->bs->exact_filename,
|
||||
s->test_file->bs->exact_filename);
|
||||
if (ret >= sizeof(bs->exact_filename)) {
|
||||
/* An overflow makes the filename unusable, so do not report any */
|
||||
bs->exact_filename[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1099,9 +1099,9 @@ int blk_pread_unthrottled(BlockBackend *blk, int64_t offset, uint8_t *buf,
|
|||
}
|
||||
|
||||
int blk_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
||||
int count, BdrvRequestFlags flags)
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
return blk_prw(blk, offset, NULL, count, blk_write_entry,
|
||||
return blk_prw(blk, offset, NULL, bytes, blk_write_entry,
|
||||
flags | BDRV_REQ_ZERO_WRITE);
|
||||
}
|
||||
|
||||
|
@ -1311,10 +1311,10 @@ static void blk_aio_pdiscard_entry(void *opaque)
|
|||
}
|
||||
|
||||
BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk,
|
||||
int64_t offset, int count,
|
||||
int64_t offset, int bytes,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
return blk_aio_prwv(blk, offset, count, NULL, blk_aio_pdiscard_entry, 0,
|
||||
return blk_aio_prwv(blk, offset, bytes, NULL, blk_aio_pdiscard_entry, 0,
|
||||
cb, opaque);
|
||||
}
|
||||
|
||||
|
@ -1374,14 +1374,14 @@ BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
|
|||
return blk_aio_prwv(blk, req, 0, &qiov, blk_aio_ioctl_entry, 0, cb, opaque);
|
||||
}
|
||||
|
||||
int blk_co_pdiscard(BlockBackend *blk, int64_t offset, int count)
|
||||
int blk_co_pdiscard(BlockBackend *blk, int64_t offset, int bytes)
|
||||
{
|
||||
int ret = blk_check_byte_request(blk, offset, count);
|
||||
int ret = blk_check_byte_request(blk, offset, bytes);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_co_pdiscard(blk_bs(blk), offset, count);
|
||||
return bdrv_co_pdiscard(blk_bs(blk), offset, bytes);
|
||||
}
|
||||
|
||||
int blk_co_flush(BlockBackend *blk)
|
||||
|
@ -1760,9 +1760,9 @@ void *blk_aio_get(const AIOCBInfo *aiocb_info, BlockBackend *blk,
|
|||
}
|
||||
|
||||
int coroutine_fn blk_co_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
||||
int count, BdrvRequestFlags flags)
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
return blk_co_pwritev(blk, offset, count, NULL,
|
||||
return blk_co_pwritev(blk, offset, bytes, NULL,
|
||||
flags | BDRV_REQ_ZERO_WRITE);
|
||||
}
|
||||
|
||||
|
@ -1789,9 +1789,9 @@ static void blk_pdiscard_entry(void *opaque)
|
|||
rwco->ret = blk_co_pdiscard(rwco->blk, rwco->offset, rwco->qiov->size);
|
||||
}
|
||||
|
||||
int blk_pdiscard(BlockBackend *blk, int64_t offset, int count)
|
||||
int blk_pdiscard(BlockBackend *blk, int64_t offset, int bytes)
|
||||
{
|
||||
return blk_prw(blk, offset, NULL, count, blk_pdiscard_entry, 0);
|
||||
return blk_prw(blk, offset, NULL, bytes, blk_pdiscard_entry, 0);
|
||||
}
|
||||
|
||||
int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf,
|
||||
|
|
|
@ -119,6 +119,13 @@ static void commit_complete(BlockJob *job, void *opaque)
|
|||
}
|
||||
g_free(s->backing_file_str);
|
||||
blk_unref(s->top);
|
||||
|
||||
/* If there is more than one reference to the job (e.g. if called from
|
||||
* block_job_finish_sync()), block_job_completed() won't free it and
|
||||
* therefore the blockers on the intermediate nodes remain. This would
|
||||
* cause bdrv_set_backing_hd() to fail. */
|
||||
block_job_remove_all_bdrv(job);
|
||||
|
||||
block_job_completed(&s->common, ret);
|
||||
g_free(data);
|
||||
|
||||
|
|
|
@ -1485,7 +1485,7 @@ static int aio_worker(void *arg)
|
|||
|
||||
static int paio_submit_co(BlockDriverState *bs, int fd,
|
||||
int64_t offset, QEMUIOVector *qiov,
|
||||
int count, int type)
|
||||
int bytes, int type)
|
||||
{
|
||||
RawPosixAIOData *acb = g_new(RawPosixAIOData, 1);
|
||||
ThreadPool *pool;
|
||||
|
@ -1494,22 +1494,22 @@ static int paio_submit_co(BlockDriverState *bs, int fd,
|
|||
acb->aio_type = type;
|
||||
acb->aio_fildes = fd;
|
||||
|
||||
acb->aio_nbytes = count;
|
||||
acb->aio_nbytes = bytes;
|
||||
acb->aio_offset = offset;
|
||||
|
||||
if (qiov) {
|
||||
acb->aio_iov = qiov->iov;
|
||||
acb->aio_niov = qiov->niov;
|
||||
assert(qiov->size == count);
|
||||
assert(qiov->size == bytes);
|
||||
}
|
||||
|
||||
trace_paio_submit_co(offset, count, type);
|
||||
trace_paio_submit_co(offset, bytes, type);
|
||||
pool = aio_get_thread_pool(bdrv_get_aio_context(bs));
|
||||
return thread_pool_submit_co(pool, aio_worker, acb);
|
||||
}
|
||||
|
||||
static BlockAIOCB *paio_submit(BlockDriverState *bs, int fd,
|
||||
int64_t offset, QEMUIOVector *qiov, int count,
|
||||
int64_t offset, QEMUIOVector *qiov, int bytes,
|
||||
BlockCompletionFunc *cb, void *opaque, int type)
|
||||
{
|
||||
RawPosixAIOData *acb = g_new(RawPosixAIOData, 1);
|
||||
|
@ -1519,7 +1519,7 @@ static BlockAIOCB *paio_submit(BlockDriverState *bs, int fd,
|
|||
acb->aio_type = type;
|
||||
acb->aio_fildes = fd;
|
||||
|
||||
acb->aio_nbytes = count;
|
||||
acb->aio_nbytes = bytes;
|
||||
acb->aio_offset = offset;
|
||||
|
||||
if (qiov) {
|
||||
|
@ -1528,7 +1528,7 @@ static BlockAIOCB *paio_submit(BlockDriverState *bs, int fd,
|
|||
assert(qiov->size == acb->aio_nbytes);
|
||||
}
|
||||
|
||||
trace_paio_submit(acb, opaque, offset, count, type);
|
||||
trace_paio_submit(acb, opaque, offset, bytes, type);
|
||||
pool = aio_get_thread_pool(bdrv_get_aio_context(bs));
|
||||
return thread_pool_submit_aio(pool, aio_worker, acb, cb, opaque);
|
||||
}
|
||||
|
@ -2109,26 +2109,26 @@ static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
|
|||
}
|
||||
|
||||
static coroutine_fn BlockAIOCB *raw_aio_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int count,
|
||||
int64_t offset, int bytes,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
||||
return paio_submit(bs, s->fd, offset, NULL, count,
|
||||
return paio_submit(bs, s->fd, offset, NULL, bytes,
|
||||
cb, opaque, QEMU_AIO_DISCARD);
|
||||
}
|
||||
|
||||
static int coroutine_fn raw_co_pwrite_zeroes(
|
||||
BlockDriverState *bs, int64_t offset,
|
||||
int count, BdrvRequestFlags flags)
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
||||
if (!(flags & BDRV_REQ_MAY_UNMAP)) {
|
||||
return paio_submit_co(bs, s->fd, offset, NULL, count,
|
||||
return paio_submit_co(bs, s->fd, offset, NULL, bytes,
|
||||
QEMU_AIO_WRITE_ZEROES);
|
||||
} else if (s->discard_zeroes) {
|
||||
return paio_submit_co(bs, s->fd, offset, NULL, count,
|
||||
return paio_submit_co(bs, s->fd, offset, NULL, bytes,
|
||||
QEMU_AIO_DISCARD);
|
||||
}
|
||||
return -ENOTSUP;
|
||||
|
@ -2560,7 +2560,7 @@ static int fd_open(BlockDriverState *bs)
|
|||
}
|
||||
|
||||
static coroutine_fn BlockAIOCB *hdev_aio_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int count,
|
||||
int64_t offset, int bytes,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
@ -2568,12 +2568,12 @@ static coroutine_fn BlockAIOCB *hdev_aio_pdiscard(BlockDriverState *bs,
|
|||
if (fd_open(bs) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
return paio_submit(bs, s->fd, offset, NULL, count,
|
||||
return paio_submit(bs, s->fd, offset, NULL, bytes,
|
||||
cb, opaque, QEMU_AIO_DISCARD|QEMU_AIO_BLKDEV);
|
||||
}
|
||||
|
||||
static coroutine_fn int hdev_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int count, BdrvRequestFlags flags)
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
int rc;
|
||||
|
@ -2583,10 +2583,10 @@ static coroutine_fn int hdev_co_pwrite_zeroes(BlockDriverState *bs,
|
|||
return rc;
|
||||
}
|
||||
if (!(flags & BDRV_REQ_MAY_UNMAP)) {
|
||||
return paio_submit_co(bs, s->fd, offset, NULL, count,
|
||||
return paio_submit_co(bs, s->fd, offset, NULL, bytes,
|
||||
QEMU_AIO_WRITE_ZEROES|QEMU_AIO_BLKDEV);
|
||||
} else if (s->discard_zeroes) {
|
||||
return paio_submit_co(bs, s->fd, offset, NULL, count,
|
||||
return paio_submit_co(bs, s->fd, offset, NULL, bytes,
|
||||
QEMU_AIO_DISCARD|QEMU_AIO_BLKDEV);
|
||||
}
|
||||
return -ENOTSUP;
|
||||
|
|
240
block/io.c
240
block/io.c
|
@ -34,16 +34,8 @@
|
|||
|
||||
#define NOT_DONE 0x7fffffff /* used while emulated sync operation in progress */
|
||||
|
||||
static BlockAIOCB *bdrv_co_aio_prw_vector(BdrvChild *child,
|
||||
int64_t offset,
|
||||
QEMUIOVector *qiov,
|
||||
BdrvRequestFlags flags,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque,
|
||||
bool is_write);
|
||||
static void coroutine_fn bdrv_co_do_rw(void *opaque);
|
||||
static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int count, BdrvRequestFlags flags);
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags);
|
||||
|
||||
void bdrv_parent_drained_begin(BlockDriverState *bs)
|
||||
{
|
||||
|
@ -674,12 +666,12 @@ int bdrv_write(BdrvChild *child, int64_t sector_num,
|
|||
}
|
||||
|
||||
int bdrv_pwrite_zeroes(BdrvChild *child, int64_t offset,
|
||||
int count, BdrvRequestFlags flags)
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
QEMUIOVector qiov;
|
||||
struct iovec iov = {
|
||||
.iov_base = NULL,
|
||||
.iov_len = count,
|
||||
.iov_len = bytes,
|
||||
};
|
||||
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
@ -1220,7 +1212,7 @@ int coroutine_fn bdrv_co_readv(BdrvChild *child, int64_t sector_num,
|
|||
#define MAX_WRITE_ZEROES_BOUNCE_BUFFER (32768 << BDRV_SECTOR_BITS)
|
||||
|
||||
static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int count, BdrvRequestFlags flags)
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
BlockDriver *drv = bs->drv;
|
||||
QEMUIOVector qiov;
|
||||
|
@ -1238,12 +1230,12 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
|
|||
|
||||
assert(alignment % bs->bl.request_alignment == 0);
|
||||
head = offset % alignment;
|
||||
tail = (offset + count) % alignment;
|
||||
tail = (offset + bytes) % alignment;
|
||||
max_write_zeroes = QEMU_ALIGN_DOWN(max_write_zeroes, alignment);
|
||||
assert(max_write_zeroes >= bs->bl.request_alignment);
|
||||
|
||||
while (count > 0 && !ret) {
|
||||
int num = count;
|
||||
while (bytes > 0 && !ret) {
|
||||
int num = bytes;
|
||||
|
||||
/* Align request. Block drivers can expect the "bulk" of the request
|
||||
* to be aligned, and that unaligned requests do not cross cluster
|
||||
|
@ -1253,7 +1245,7 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
|
|||
/* Make a small request up to the first aligned sector. For
|
||||
* convenience, limit this request to max_transfer even if
|
||||
* we don't need to fall back to writes. */
|
||||
num = MIN(MIN(count, max_transfer), alignment - head);
|
||||
num = MIN(MIN(bytes, max_transfer), alignment - head);
|
||||
head = (head + num) % alignment;
|
||||
assert(num < max_write_zeroes);
|
||||
} else if (tail && num > alignment) {
|
||||
|
@ -1314,7 +1306,7 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
|
|||
}
|
||||
|
||||
offset += num;
|
||||
count -= num;
|
||||
bytes -= num;
|
||||
}
|
||||
|
||||
fail:
|
||||
|
@ -1666,15 +1658,15 @@ int coroutine_fn bdrv_co_writev(BdrvChild *child, int64_t sector_num,
|
|||
}
|
||||
|
||||
int coroutine_fn bdrv_co_pwrite_zeroes(BdrvChild *child, int64_t offset,
|
||||
int count, BdrvRequestFlags flags)
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
trace_bdrv_co_pwrite_zeroes(child->bs, offset, count, flags);
|
||||
trace_bdrv_co_pwrite_zeroes(child->bs, offset, bytes, flags);
|
||||
|
||||
if (!(child->bs->open_flags & BDRV_O_UNMAP)) {
|
||||
flags &= ~BDRV_REQ_MAY_UNMAP;
|
||||
}
|
||||
|
||||
return bdrv_co_pwritev(child, offset, count, NULL,
|
||||
return bdrv_co_pwritev(child, offset, bytes, NULL,
|
||||
BDRV_REQ_ZERO_WRITE | flags);
|
||||
}
|
||||
|
||||
|
@ -1980,17 +1972,24 @@ bdrv_co_rw_vmstate(BlockDriverState *bs, QEMUIOVector *qiov, int64_t pos,
|
|||
bool is_read)
|
||||
{
|
||||
BlockDriver *drv = bs->drv;
|
||||
int ret = -ENOTSUP;
|
||||
|
||||
bdrv_inc_in_flight(bs);
|
||||
|
||||
if (!drv) {
|
||||
return -ENOMEDIUM;
|
||||
ret = -ENOMEDIUM;
|
||||
} else if (drv->bdrv_load_vmstate) {
|
||||
return is_read ? drv->bdrv_load_vmstate(bs, qiov, pos)
|
||||
: drv->bdrv_save_vmstate(bs, qiov, pos);
|
||||
if (is_read) {
|
||||
ret = drv->bdrv_load_vmstate(bs, qiov, pos);
|
||||
} else {
|
||||
ret = drv->bdrv_save_vmstate(bs, qiov, pos);
|
||||
}
|
||||
} else if (bs->file) {
|
||||
return bdrv_co_rw_vmstate(bs->file->bs, qiov, pos, is_read);
|
||||
ret = bdrv_co_rw_vmstate(bs->file->bs, qiov, pos, is_read);
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
bdrv_dec_in_flight(bs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void coroutine_fn bdrv_co_rw_vmstate_entry(void *opaque)
|
||||
|
@ -2016,9 +2015,7 @@ bdrv_rw_vmstate(BlockDriverState *bs, QEMUIOVector *qiov, int64_t pos,
|
|||
Coroutine *co = qemu_coroutine_create(bdrv_co_rw_vmstate_entry, &data);
|
||||
|
||||
bdrv_coroutine_enter(bs, co);
|
||||
while (data.ret == -EINPROGRESS) {
|
||||
aio_poll(bdrv_get_aio_context(bs), true);
|
||||
}
|
||||
BDRV_POLL_WHILE(bs, data.ret == -EINPROGRESS);
|
||||
return data.ret;
|
||||
}
|
||||
}
|
||||
|
@ -2075,28 +2072,6 @@ int bdrv_readv_vmstate(BlockDriverState *bs, QEMUIOVector *qiov, int64_t pos)
|
|||
/**************************************************************/
|
||||
/* async I/Os */
|
||||
|
||||
BlockAIOCB *bdrv_aio_readv(BdrvChild *child, int64_t sector_num,
|
||||
QEMUIOVector *qiov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
trace_bdrv_aio_readv(child->bs, sector_num, nb_sectors, opaque);
|
||||
|
||||
assert(nb_sectors << BDRV_SECTOR_BITS == qiov->size);
|
||||
return bdrv_co_aio_prw_vector(child, sector_num << BDRV_SECTOR_BITS, qiov,
|
||||
0, cb, opaque, false);
|
||||
}
|
||||
|
||||
BlockAIOCB *bdrv_aio_writev(BdrvChild *child, int64_t sector_num,
|
||||
QEMUIOVector *qiov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
trace_bdrv_aio_writev(child->bs, sector_num, nb_sectors, opaque);
|
||||
|
||||
assert(nb_sectors << BDRV_SECTOR_BITS == qiov->size);
|
||||
return bdrv_co_aio_prw_vector(child, sector_num << BDRV_SECTOR_BITS, qiov,
|
||||
0, cb, opaque, true);
|
||||
}
|
||||
|
||||
void bdrv_aio_cancel(BlockAIOCB *acb)
|
||||
{
|
||||
qemu_aio_ref(acb);
|
||||
|
@ -2128,147 +2103,6 @@ void bdrv_aio_cancel_async(BlockAIOCB *acb)
|
|||
}
|
||||
}
|
||||
|
||||
/**************************************************************/
|
||||
/* async block device emulation */
|
||||
|
||||
typedef struct BlockRequest {
|
||||
union {
|
||||
/* Used during read, write, trim */
|
||||
struct {
|
||||
int64_t offset;
|
||||
int bytes;
|
||||
int flags;
|
||||
QEMUIOVector *qiov;
|
||||
};
|
||||
/* Used during ioctl */
|
||||
struct {
|
||||
int req;
|
||||
void *buf;
|
||||
};
|
||||
};
|
||||
BlockCompletionFunc *cb;
|
||||
void *opaque;
|
||||
|
||||
int error;
|
||||
} BlockRequest;
|
||||
|
||||
typedef struct BlockAIOCBCoroutine {
|
||||
BlockAIOCB common;
|
||||
BdrvChild *child;
|
||||
BlockRequest req;
|
||||
bool is_write;
|
||||
bool need_bh;
|
||||
bool *done;
|
||||
} BlockAIOCBCoroutine;
|
||||
|
||||
static const AIOCBInfo bdrv_em_co_aiocb_info = {
|
||||
.aiocb_size = sizeof(BlockAIOCBCoroutine),
|
||||
};
|
||||
|
||||
static void bdrv_co_complete(BlockAIOCBCoroutine *acb)
|
||||
{
|
||||
if (!acb->need_bh) {
|
||||
bdrv_dec_in_flight(acb->common.bs);
|
||||
acb->common.cb(acb->common.opaque, acb->req.error);
|
||||
qemu_aio_unref(acb);
|
||||
}
|
||||
}
|
||||
|
||||
static void bdrv_co_em_bh(void *opaque)
|
||||
{
|
||||
BlockAIOCBCoroutine *acb = opaque;
|
||||
|
||||
assert(!acb->need_bh);
|
||||
bdrv_co_complete(acb);
|
||||
}
|
||||
|
||||
static void bdrv_co_maybe_schedule_bh(BlockAIOCBCoroutine *acb)
|
||||
{
|
||||
acb->need_bh = false;
|
||||
if (acb->req.error != -EINPROGRESS) {
|
||||
BlockDriverState *bs = acb->common.bs;
|
||||
|
||||
aio_bh_schedule_oneshot(bdrv_get_aio_context(bs), bdrv_co_em_bh, acb);
|
||||
}
|
||||
}
|
||||
|
||||
/* Invoke bdrv_co_do_readv/bdrv_co_do_writev */
|
||||
static void coroutine_fn bdrv_co_do_rw(void *opaque)
|
||||
{
|
||||
BlockAIOCBCoroutine *acb = opaque;
|
||||
|
||||
if (!acb->is_write) {
|
||||
acb->req.error = bdrv_co_preadv(acb->child, acb->req.offset,
|
||||
acb->req.qiov->size, acb->req.qiov, acb->req.flags);
|
||||
} else {
|
||||
acb->req.error = bdrv_co_pwritev(acb->child, acb->req.offset,
|
||||
acb->req.qiov->size, acb->req.qiov, acb->req.flags);
|
||||
}
|
||||
|
||||
bdrv_co_complete(acb);
|
||||
}
|
||||
|
||||
static BlockAIOCB *bdrv_co_aio_prw_vector(BdrvChild *child,
|
||||
int64_t offset,
|
||||
QEMUIOVector *qiov,
|
||||
BdrvRequestFlags flags,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque,
|
||||
bool is_write)
|
||||
{
|
||||
Coroutine *co;
|
||||
BlockAIOCBCoroutine *acb;
|
||||
|
||||
/* Matched by bdrv_co_complete's bdrv_dec_in_flight. */
|
||||
bdrv_inc_in_flight(child->bs);
|
||||
|
||||
acb = qemu_aio_get(&bdrv_em_co_aiocb_info, child->bs, cb, opaque);
|
||||
acb->child = child;
|
||||
acb->need_bh = true;
|
||||
acb->req.error = -EINPROGRESS;
|
||||
acb->req.offset = offset;
|
||||
acb->req.qiov = qiov;
|
||||
acb->req.flags = flags;
|
||||
acb->is_write = is_write;
|
||||
|
||||
co = qemu_coroutine_create(bdrv_co_do_rw, acb);
|
||||
bdrv_coroutine_enter(child->bs, co);
|
||||
|
||||
bdrv_co_maybe_schedule_bh(acb);
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static void coroutine_fn bdrv_aio_flush_co_entry(void *opaque)
|
||||
{
|
||||
BlockAIOCBCoroutine *acb = opaque;
|
||||
BlockDriverState *bs = acb->common.bs;
|
||||
|
||||
acb->req.error = bdrv_co_flush(bs);
|
||||
bdrv_co_complete(acb);
|
||||
}
|
||||
|
||||
BlockAIOCB *bdrv_aio_flush(BlockDriverState *bs,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
trace_bdrv_aio_flush(bs, opaque);
|
||||
|
||||
Coroutine *co;
|
||||
BlockAIOCBCoroutine *acb;
|
||||
|
||||
/* Matched by bdrv_co_complete's bdrv_dec_in_flight. */
|
||||
bdrv_inc_in_flight(bs);
|
||||
|
||||
acb = qemu_aio_get(&bdrv_em_co_aiocb_info, bs, cb, opaque);
|
||||
acb->need_bh = true;
|
||||
acb->req.error = -EINPROGRESS;
|
||||
|
||||
co = qemu_coroutine_create(bdrv_aio_flush_co_entry, acb);
|
||||
bdrv_coroutine_enter(bs, co);
|
||||
|
||||
bdrv_co_maybe_schedule_bh(acb);
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
/**************************************************************/
|
||||
/* Coroutine block device emulation */
|
||||
|
||||
|
@ -2414,18 +2248,18 @@ int bdrv_flush(BlockDriverState *bs)
|
|||
typedef struct DiscardCo {
|
||||
BlockDriverState *bs;
|
||||
int64_t offset;
|
||||
int count;
|
||||
int bytes;
|
||||
int ret;
|
||||
} DiscardCo;
|
||||
static void coroutine_fn bdrv_pdiscard_co_entry(void *opaque)
|
||||
{
|
||||
DiscardCo *rwco = opaque;
|
||||
|
||||
rwco->ret = bdrv_co_pdiscard(rwco->bs, rwco->offset, rwco->count);
|
||||
rwco->ret = bdrv_co_pdiscard(rwco->bs, rwco->offset, rwco->bytes);
|
||||
}
|
||||
|
||||
int coroutine_fn bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset,
|
||||
int count)
|
||||
int bytes)
|
||||
{
|
||||
BdrvTrackedRequest req;
|
||||
int max_pdiscard, ret;
|
||||
|
@ -2435,7 +2269,7 @@ int coroutine_fn bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset,
|
|||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
ret = bdrv_check_byte_request(bs, offset, count);
|
||||
ret = bdrv_check_byte_request(bs, offset, bytes);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
} else if (bs->read_only) {
|
||||
|
@ -2460,10 +2294,10 @@ int coroutine_fn bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset,
|
|||
align = MAX(bs->bl.pdiscard_alignment, bs->bl.request_alignment);
|
||||
assert(align % bs->bl.request_alignment == 0);
|
||||
head = offset % align;
|
||||
tail = (offset + count) % align;
|
||||
tail = (offset + bytes) % align;
|
||||
|
||||
bdrv_inc_in_flight(bs);
|
||||
tracked_request_begin(&req, bs, offset, count, BDRV_TRACKED_DISCARD);
|
||||
tracked_request_begin(&req, bs, offset, bytes, BDRV_TRACKED_DISCARD);
|
||||
|
||||
ret = notifier_with_return_list_notify(&bs->before_write_notifiers, &req);
|
||||
if (ret < 0) {
|
||||
|
@ -2474,13 +2308,13 @@ int coroutine_fn bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset,
|
|||
align);
|
||||
assert(max_pdiscard >= bs->bl.request_alignment);
|
||||
|
||||
while (count > 0) {
|
||||
while (bytes > 0) {
|
||||
int ret;
|
||||
int num = count;
|
||||
int num = bytes;
|
||||
|
||||
if (head) {
|
||||
/* Make small requests to get to alignment boundaries. */
|
||||
num = MIN(count, align - head);
|
||||
num = MIN(bytes, align - head);
|
||||
if (!QEMU_IS_ALIGNED(num, bs->bl.request_alignment)) {
|
||||
num %= bs->bl.request_alignment;
|
||||
}
|
||||
|
@ -2524,7 +2358,7 @@ int coroutine_fn bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset,
|
|||
}
|
||||
|
||||
offset += num;
|
||||
count -= num;
|
||||
bytes -= num;
|
||||
}
|
||||
ret = 0;
|
||||
out:
|
||||
|
@ -2536,13 +2370,13 @@ out:
|
|||
return ret;
|
||||
}
|
||||
|
||||
int bdrv_pdiscard(BlockDriverState *bs, int64_t offset, int count)
|
||||
int bdrv_pdiscard(BlockDriverState *bs, int64_t offset, int bytes)
|
||||
{
|
||||
Coroutine *co;
|
||||
DiscardCo rwco = {
|
||||
.bs = bs,
|
||||
.offset = offset,
|
||||
.count = count,
|
||||
.bytes = bytes,
|
||||
.ret = NOT_DONE,
|
||||
};
|
||||
|
||||
|
|
|
@ -1116,14 +1116,14 @@ iscsi_getlength(BlockDriverState *bs)
|
|||
}
|
||||
|
||||
static int
|
||||
coroutine_fn iscsi_co_pdiscard(BlockDriverState *bs, int64_t offset, int count)
|
||||
coroutine_fn iscsi_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes)
|
||||
{
|
||||
IscsiLun *iscsilun = bs->opaque;
|
||||
struct IscsiTask iTask;
|
||||
struct unmap_list list;
|
||||
int r = 0;
|
||||
|
||||
if (!is_byte_request_lun_aligned(offset, count, iscsilun)) {
|
||||
if (!is_byte_request_lun_aligned(offset, bytes, iscsilun)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
|
@ -1133,7 +1133,7 @@ coroutine_fn iscsi_co_pdiscard(BlockDriverState *bs, int64_t offset, int count)
|
|||
}
|
||||
|
||||
list.lba = offset / iscsilun->block_size;
|
||||
list.num = count / iscsilun->block_size;
|
||||
list.num = bytes / iscsilun->block_size;
|
||||
|
||||
iscsi_co_init_iscsitask(iscsilun, &iTask);
|
||||
qemu_mutex_lock(&iscsilun->mutex);
|
||||
|
@ -1174,7 +1174,7 @@ retry:
|
|||
}
|
||||
|
||||
iscsi_allocmap_set_invalid(iscsilun, offset >> BDRV_SECTOR_BITS,
|
||||
count >> BDRV_SECTOR_BITS);
|
||||
bytes >> BDRV_SECTOR_BITS);
|
||||
|
||||
out_unlock:
|
||||
qemu_mutex_unlock(&iscsilun->mutex);
|
||||
|
@ -1183,7 +1183,7 @@ out_unlock:
|
|||
|
||||
static int
|
||||
coroutine_fn iscsi_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
||||
int count, BdrvRequestFlags flags)
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
IscsiLun *iscsilun = bs->opaque;
|
||||
struct IscsiTask iTask;
|
||||
|
@ -1192,7 +1192,7 @@ coroutine_fn iscsi_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
|||
bool use_16_for_ws = iscsilun->use_16_for_rw;
|
||||
int r = 0;
|
||||
|
||||
if (!is_byte_request_lun_aligned(offset, count, iscsilun)) {
|
||||
if (!is_byte_request_lun_aligned(offset, bytes, iscsilun)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
|
@ -1215,7 +1215,7 @@ coroutine_fn iscsi_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
|||
}
|
||||
|
||||
lba = offset / iscsilun->block_size;
|
||||
nb_blocks = count / iscsilun->block_size;
|
||||
nb_blocks = bytes / iscsilun->block_size;
|
||||
|
||||
if (iscsilun->zeroblock == NULL) {
|
||||
iscsilun->zeroblock = g_try_malloc0(iscsilun->block_size);
|
||||
|
@ -1273,17 +1273,17 @@ retry:
|
|||
|
||||
if (iTask.status != SCSI_STATUS_GOOD) {
|
||||
iscsi_allocmap_set_invalid(iscsilun, offset >> BDRV_SECTOR_BITS,
|
||||
count >> BDRV_SECTOR_BITS);
|
||||
bytes >> BDRV_SECTOR_BITS);
|
||||
r = iTask.err_code;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (flags & BDRV_REQ_MAY_UNMAP) {
|
||||
iscsi_allocmap_set_invalid(iscsilun, offset >> BDRV_SECTOR_BITS,
|
||||
count >> BDRV_SECTOR_BITS);
|
||||
bytes >> BDRV_SECTOR_BITS);
|
||||
} else {
|
||||
iscsi_allocmap_set_allocated(iscsilun, offset >> BDRV_SECTOR_BITS,
|
||||
count >> BDRV_SECTOR_BITS);
|
||||
bytes >> BDRV_SECTOR_BITS);
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
|
|
|
@ -1063,15 +1063,15 @@ static int64_t coroutine_fn bdrv_mirror_top_get_block_status(
|
|||
}
|
||||
|
||||
static int coroutine_fn bdrv_mirror_top_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int count, BdrvRequestFlags flags)
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
return bdrv_co_pwrite_zeroes(bs->backing, offset, count, flags);
|
||||
return bdrv_co_pwrite_zeroes(bs->backing, offset, bytes, flags);
|
||||
}
|
||||
|
||||
static int coroutine_fn bdrv_mirror_top_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int count)
|
||||
int64_t offset, int bytes)
|
||||
{
|
||||
return bdrv_co_pdiscard(bs->backing->bs, offset, count);
|
||||
return bdrv_co_pdiscard(bs->backing->bs, offset, bytes);
|
||||
}
|
||||
|
||||
static void bdrv_mirror_top_refresh_filename(BlockDriverState *bs, QDict *opts)
|
||||
|
|
|
@ -259,14 +259,14 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
|||
}
|
||||
|
||||
int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
||||
int count, BdrvRequestFlags flags)
|
||||
int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
ssize_t ret;
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NBDRequest request = {
|
||||
.type = NBD_CMD_WRITE_ZEROES,
|
||||
.from = offset,
|
||||
.len = count,
|
||||
.len = bytes,
|
||||
};
|
||||
NBDReply reply;
|
||||
|
||||
|
@ -316,13 +316,13 @@ int nbd_client_co_flush(BlockDriverState *bs)
|
|||
return -reply.error;
|
||||
}
|
||||
|
||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count)
|
||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes)
|
||||
{
|
||||
NBDClientSession *client = nbd_get_client_session(bs);
|
||||
NBDRequest request = {
|
||||
.type = NBD_CMD_TRIM,
|
||||
.from = offset,
|
||||
.len = count,
|
||||
.len = bytes,
|
||||
};
|
||||
NBDReply reply;
|
||||
ssize_t ret;
|
||||
|
|
|
@ -42,12 +42,12 @@ int nbd_client_init(BlockDriverState *bs,
|
|||
Error **errp);
|
||||
void nbd_client_close(BlockDriverState *bs);
|
||||
|
||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count);
|
||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes);
|
||||
int nbd_client_co_flush(BlockDriverState *bs);
|
||||
int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, int flags);
|
||||
int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
||||
int count, BdrvRequestFlags flags);
|
||||
int bytes, BdrvRequestFlags flags);
|
||||
int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov, int flags);
|
||||
|
||||
|
|
|
@ -64,11 +64,11 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
|||
}
|
||||
|
||||
/* transport */
|
||||
if (!strcmp(uri->scheme, "nbd")) {
|
||||
if (!g_strcmp0(uri->scheme, "nbd")) {
|
||||
is_unix = false;
|
||||
} else if (!strcmp(uri->scheme, "nbd+tcp")) {
|
||||
} else if (!g_strcmp0(uri->scheme, "nbd+tcp")) {
|
||||
is_unix = false;
|
||||
} else if (!strcmp(uri->scheme, "nbd+unix")) {
|
||||
} else if (!g_strcmp0(uri->scheme, "nbd+unix")) {
|
||||
is_unix = true;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
|
|
|
@ -82,7 +82,7 @@ static int nfs_parse_uri(const char *filename, QDict *options, Error **errp)
|
|||
error_setg(errp, "Invalid URI specified");
|
||||
goto out;
|
||||
}
|
||||
if (strcmp(uri->scheme, "nfs") != 0) {
|
||||
if (g_strcmp0(uri->scheme, "nfs") != 0) {
|
||||
error_setg(errp, "URI scheme must be 'nfs'");
|
||||
goto out;
|
||||
}
|
||||
|
|
|
@ -403,30 +403,21 @@ int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int coroutine_fn do_perform_cow(BlockDriverState *bs,
|
||||
static int coroutine_fn do_perform_cow_read(BlockDriverState *bs,
|
||||
uint64_t src_cluster_offset,
|
||||
uint64_t cluster_offset,
|
||||
int offset_in_cluster,
|
||||
int bytes)
|
||||
unsigned offset_in_cluster,
|
||||
QEMUIOVector *qiov)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
QEMUIOVector qiov;
|
||||
struct iovec iov;
|
||||
int ret;
|
||||
|
||||
iov.iov_len = bytes;
|
||||
iov.iov_base = qemu_try_blockalign(bs, iov.iov_len);
|
||||
if (iov.iov_base == NULL) {
|
||||
return -ENOMEM;
|
||||
if (qiov->size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_COW_READ);
|
||||
|
||||
if (!bs->drv) {
|
||||
ret = -ENOMEDIUM;
|
||||
goto out;
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
/* Call .bdrv_co_readv() directly instead of using the public block-layer
|
||||
|
@ -434,43 +425,60 @@ static int coroutine_fn do_perform_cow(BlockDriverState *bs,
|
|||
* which can lead to deadlock when block layer copy-on-read is enabled.
|
||||
*/
|
||||
ret = bs->drv->bdrv_co_preadv(bs, src_cluster_offset + offset_in_cluster,
|
||||
bytes, &qiov, 0);
|
||||
qiov->size, qiov, 0);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (bs->encrypted) {
|
||||
Error *err = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
|
||||
uint64_t src_cluster_offset,
|
||||
unsigned offset_in_cluster,
|
||||
uint8_t *buffer,
|
||||
unsigned bytes)
|
||||
{
|
||||
if (bytes && bs->encrypted) {
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
int64_t sector = (src_cluster_offset + offset_in_cluster)
|
||||
>> BDRV_SECTOR_BITS;
|
||||
assert(s->cipher);
|
||||
assert((offset_in_cluster & ~BDRV_SECTOR_MASK) == 0);
|
||||
assert((bytes & ~BDRV_SECTOR_MASK) == 0);
|
||||
if (qcow2_encrypt_sectors(s, sector, iov.iov_base, iov.iov_base,
|
||||
bytes >> BDRV_SECTOR_BITS, true, &err) < 0) {
|
||||
ret = -EIO;
|
||||
error_free(err);
|
||||
goto out;
|
||||
if (qcow2_encrypt_sectors(s, sector, buffer, buffer,
|
||||
bytes >> BDRV_SECTOR_BITS, true, NULL) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int coroutine_fn do_perform_cow_write(BlockDriverState *bs,
|
||||
uint64_t cluster_offset,
|
||||
unsigned offset_in_cluster,
|
||||
QEMUIOVector *qiov)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (qiov->size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = qcow2_pre_write_overlap_check(bs, 0,
|
||||
cluster_offset + offset_in_cluster, bytes);
|
||||
cluster_offset + offset_in_cluster, qiov->size);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_COW_WRITE);
|
||||
ret = bdrv_co_pwritev(bs->file, cluster_offset + offset_in_cluster,
|
||||
bytes, &qiov, 0);
|
||||
qiov->size, qiov, 0);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
qemu_vfree(iov.iov_base);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -548,7 +556,7 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
|||
|
||||
/* find the cluster offset for the given disk offset */
|
||||
|
||||
l2_index = (offset >> s->cluster_bits) & (s->l2_size - 1);
|
||||
l2_index = offset_to_l2_index(s, offset);
|
||||
*cluster_offset = be64_to_cpu(l2_table[l2_index]);
|
||||
|
||||
nb_clusters = size_to_clusters(s, bytes_needed);
|
||||
|
@ -685,7 +693,7 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
|||
|
||||
/* find the cluster offset for the given disk offset */
|
||||
|
||||
l2_index = (offset >> s->cluster_bits) & (s->l2_size - 1);
|
||||
l2_index = offset_to_l2_index(s, offset);
|
||||
|
||||
*new_l2_table = l2_table;
|
||||
*new_l2_index = l2_index;
|
||||
|
@ -753,31 +761,133 @@ uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
|||
return cluster_offset;
|
||||
}
|
||||
|
||||
static int perform_cow(BlockDriverState *bs, QCowL2Meta *m, Qcow2COWRegion *r)
|
||||
static int perform_cow(BlockDriverState *bs, QCowL2Meta *m)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
Qcow2COWRegion *start = &m->cow_start;
|
||||
Qcow2COWRegion *end = &m->cow_end;
|
||||
unsigned buffer_size;
|
||||
unsigned data_bytes = end->offset - (start->offset + start->nb_bytes);
|
||||
bool merge_reads;
|
||||
uint8_t *start_buffer, *end_buffer;
|
||||
QEMUIOVector qiov;
|
||||
int ret;
|
||||
|
||||
if (r->nb_bytes == 0) {
|
||||
assert(start->nb_bytes <= UINT_MAX - end->nb_bytes);
|
||||
assert(start->nb_bytes + end->nb_bytes <= UINT_MAX - data_bytes);
|
||||
assert(start->offset + start->nb_bytes <= end->offset);
|
||||
assert(!m->data_qiov || m->data_qiov->size == data_bytes);
|
||||
|
||||
if (start->nb_bytes == 0 && end->nb_bytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
ret = do_perform_cow(bs, m->offset, m->alloc_offset, r->offset, r->nb_bytes);
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
/* If we have to read both the start and end COW regions and the
|
||||
* middle region is not too large then perform just one read
|
||||
* operation */
|
||||
merge_reads = start->nb_bytes && end->nb_bytes && data_bytes <= 16384;
|
||||
if (merge_reads) {
|
||||
buffer_size = start->nb_bytes + data_bytes + end->nb_bytes;
|
||||
} else {
|
||||
/* If we have to do two reads, add some padding in the middle
|
||||
* if necessary to make sure that the end region is optimally
|
||||
* aligned. */
|
||||
size_t align = bdrv_opt_mem_align(bs);
|
||||
assert(align > 0 && align <= UINT_MAX);
|
||||
assert(QEMU_ALIGN_UP(start->nb_bytes, align) <=
|
||||
UINT_MAX - end->nb_bytes);
|
||||
buffer_size = QEMU_ALIGN_UP(start->nb_bytes, align) + end->nb_bytes;
|
||||
}
|
||||
|
||||
/* Reserve a buffer large enough to store all the data that we're
|
||||
* going to read */
|
||||
start_buffer = qemu_try_blockalign(bs, buffer_size);
|
||||
if (start_buffer == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
/* The part of the buffer where the end region is located */
|
||||
end_buffer = start_buffer + buffer_size - end->nb_bytes;
|
||||
|
||||
qemu_iovec_init(&qiov, 2 + (m->data_qiov ? m->data_qiov->niov : 0));
|
||||
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
/* First we read the existing data from both COW regions. We
|
||||
* either read the whole region in one go, or the start and end
|
||||
* regions separately. */
|
||||
if (merge_reads) {
|
||||
qemu_iovec_add(&qiov, start_buffer, buffer_size);
|
||||
ret = do_perform_cow_read(bs, m->offset, start->offset, &qiov);
|
||||
} else {
|
||||
qemu_iovec_add(&qiov, start_buffer, start->nb_bytes);
|
||||
ret = do_perform_cow_read(bs, m->offset, start->offset, &qiov);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
qemu_iovec_reset(&qiov);
|
||||
qemu_iovec_add(&qiov, end_buffer, end->nb_bytes);
|
||||
ret = do_perform_cow_read(bs, m->offset, end->offset, &qiov);
|
||||
}
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Encrypt the data if necessary before writing it */
|
||||
if (bs->encrypted) {
|
||||
if (!do_perform_cow_encrypt(bs, m->offset, start->offset,
|
||||
start_buffer, start->nb_bytes) ||
|
||||
!do_perform_cow_encrypt(bs, m->offset, end->offset,
|
||||
end_buffer, end->nb_bytes)) {
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* And now we can write everything. If we have the guest data we
|
||||
* can write everything in one single operation */
|
||||
if (m->data_qiov) {
|
||||
qemu_iovec_reset(&qiov);
|
||||
if (start->nb_bytes) {
|
||||
qemu_iovec_add(&qiov, start_buffer, start->nb_bytes);
|
||||
}
|
||||
qemu_iovec_concat(&qiov, m->data_qiov, 0, data_bytes);
|
||||
if (end->nb_bytes) {
|
||||
qemu_iovec_add(&qiov, end_buffer, end->nb_bytes);
|
||||
}
|
||||
/* NOTE: we have a write_aio blkdebug event here followed by
|
||||
* a cow_write one in do_perform_cow_write(), but there's only
|
||||
* one single I/O operation */
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO);
|
||||
ret = do_perform_cow_write(bs, m->alloc_offset, start->offset, &qiov);
|
||||
} else {
|
||||
/* If there's no guest data then write both COW regions separately */
|
||||
qemu_iovec_reset(&qiov);
|
||||
qemu_iovec_add(&qiov, start_buffer, start->nb_bytes);
|
||||
ret = do_perform_cow_write(bs, m->alloc_offset, start->offset, &qiov);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
qemu_iovec_reset(&qiov);
|
||||
qemu_iovec_add(&qiov, end_buffer, end->nb_bytes);
|
||||
ret = do_perform_cow_write(bs, m->alloc_offset, end->offset, &qiov);
|
||||
}
|
||||
|
||||
fail:
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
|
||||
/*
|
||||
* Before we update the L2 table to actually point to the new cluster, we
|
||||
* need to be sure that the refcounts have been increased and COW was
|
||||
* handled.
|
||||
*/
|
||||
if (ret == 0) {
|
||||
qcow2_cache_depends_on_flush(s->l2_table_cache);
|
||||
}
|
||||
|
||||
return 0;
|
||||
qemu_vfree(start_buffer);
|
||||
qemu_iovec_destroy(&qiov);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
||||
|
@ -797,12 +907,7 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
|||
}
|
||||
|
||||
/* copy content of unmodified sectors */
|
||||
ret = perform_cow(bs, m, &m->cow_start);
|
||||
if (ret < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = perform_cow(bs, m, &m->cow_end);
|
||||
ret = perform_cow(bs, m);
|
||||
if (ret < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
|
|
@ -356,7 +356,7 @@ static int validate_table_offset(BlockDriverState *bs, uint64_t offset,
|
|||
}
|
||||
|
||||
/* Tables must be cluster aligned */
|
||||
if (offset & (s->cluster_size - 1)) {
|
||||
if (offset_into_cluster(s, offset) != 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -1575,6 +1575,44 @@ fail:
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Check if it's possible to merge a write request with the writing of
|
||||
* the data from the COW regions */
|
||||
static bool merge_cow(uint64_t offset, unsigned bytes,
|
||||
QEMUIOVector *hd_qiov, QCowL2Meta *l2meta)
|
||||
{
|
||||
QCowL2Meta *m;
|
||||
|
||||
for (m = l2meta; m != NULL; m = m->next) {
|
||||
/* If both COW regions are empty then there's nothing to merge */
|
||||
if (m->cow_start.nb_bytes == 0 && m->cow_end.nb_bytes == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The data (middle) region must be immediately after the
|
||||
* start region */
|
||||
if (l2meta_cow_start(m) + m->cow_start.nb_bytes != offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The end region must be immediately after the data (middle)
|
||||
* region */
|
||||
if (m->offset + m->cow_end.offset != offset + bytes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Make sure that adding both COW regions to the QEMUIOVector
|
||||
* does not exceed IOV_MAX */
|
||||
if (hd_qiov->niov > IOV_MAX - 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m->data_qiov = hd_qiov;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static coroutine_fn int qcow2_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, QEMUIOVector *qiov,
|
||||
int flags)
|
||||
|
@ -1657,6 +1695,11 @@ static coroutine_fn int qcow2_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
/* If we need to do COW, check if it's possible to merge the
|
||||
* writing of the guest data together with that of the COW regions.
|
||||
* If it's not possible (or not necessary) then write the
|
||||
* guest data now. */
|
||||
if (!merge_cow(offset, cur_bytes, &hd_qiov, l2meta)) {
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO);
|
||||
trace_qcow2_writev_data(qemu_coroutine_self(),
|
||||
|
@ -1668,6 +1711,7 @@ static coroutine_fn int qcow2_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
|||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
while (l2meta != NULL) {
|
||||
QCowL2Meta *next;
|
||||
|
@ -2464,16 +2508,16 @@ static bool is_zero_sectors(BlockDriverState *bs, int64_t start,
|
|||
}
|
||||
|
||||
static coroutine_fn int qcow2_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int count, BdrvRequestFlags flags)
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
int ret;
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
|
||||
uint32_t head = offset % s->cluster_size;
|
||||
uint32_t tail = (offset + count) % s->cluster_size;
|
||||
uint32_t tail = (offset + bytes) % s->cluster_size;
|
||||
|
||||
trace_qcow2_pwrite_zeroes_start_req(qemu_coroutine_self(), offset, count);
|
||||
if (offset + count == bs->total_sectors * BDRV_SECTOR_SIZE) {
|
||||
trace_qcow2_pwrite_zeroes_start_req(qemu_coroutine_self(), offset, bytes);
|
||||
if (offset + bytes == bs->total_sectors * BDRV_SECTOR_SIZE) {
|
||||
tail = 0;
|
||||
}
|
||||
|
||||
|
@ -2482,12 +2526,12 @@ static coroutine_fn int qcow2_co_pwrite_zeroes(BlockDriverState *bs,
|
|||
uint64_t off;
|
||||
unsigned int nr;
|
||||
|
||||
assert(head + count <= s->cluster_size);
|
||||
assert(head + bytes <= s->cluster_size);
|
||||
|
||||
/* check whether remainder of cluster already reads as zero */
|
||||
if (!(is_zero_sectors(bs, cl_start,
|
||||
DIV_ROUND_UP(head, BDRV_SECTOR_SIZE)) &&
|
||||
is_zero_sectors(bs, (offset + count) >> BDRV_SECTOR_BITS,
|
||||
is_zero_sectors(bs, (offset + bytes) >> BDRV_SECTOR_BITS,
|
||||
DIV_ROUND_UP(-tail & (s->cluster_size - 1),
|
||||
BDRV_SECTOR_SIZE)))) {
|
||||
return -ENOTSUP;
|
||||
|
@ -2496,7 +2540,7 @@ static coroutine_fn int qcow2_co_pwrite_zeroes(BlockDriverState *bs,
|
|||
qemu_co_mutex_lock(&s->lock);
|
||||
/* We can have new write after previous check */
|
||||
offset = cl_start << BDRV_SECTOR_BITS;
|
||||
count = s->cluster_size;
|
||||
bytes = s->cluster_size;
|
||||
nr = s->cluster_size;
|
||||
ret = qcow2_get_cluster_offset(bs, offset, &nr, &off);
|
||||
if (ret != QCOW2_CLUSTER_UNALLOCATED &&
|
||||
|
@ -2509,33 +2553,33 @@ static coroutine_fn int qcow2_co_pwrite_zeroes(BlockDriverState *bs,
|
|||
qemu_co_mutex_lock(&s->lock);
|
||||
}
|
||||
|
||||
trace_qcow2_pwrite_zeroes(qemu_coroutine_self(), offset, count);
|
||||
trace_qcow2_pwrite_zeroes(qemu_coroutine_self(), offset, bytes);
|
||||
|
||||
/* Whatever is left can use real zero clusters */
|
||||
ret = qcow2_cluster_zeroize(bs, offset, count, flags);
|
||||
ret = qcow2_cluster_zeroize(bs, offset, bytes, flags);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static coroutine_fn int qcow2_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int count)
|
||||
int64_t offset, int bytes)
|
||||
{
|
||||
int ret;
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
|
||||
if (!QEMU_IS_ALIGNED(offset | count, s->cluster_size)) {
|
||||
assert(count < s->cluster_size);
|
||||
if (!QEMU_IS_ALIGNED(offset | bytes, s->cluster_size)) {
|
||||
assert(bytes < s->cluster_size);
|
||||
/* Ignore partial clusters, except for the special case of the
|
||||
* complete partial cluster at the end of an unaligned file */
|
||||
if (!QEMU_IS_ALIGNED(offset, s->cluster_size) ||
|
||||
offset + count != bs->total_sectors * BDRV_SECTOR_SIZE) {
|
||||
offset + bytes != bs->total_sectors * BDRV_SECTOR_SIZE) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
ret = qcow2_cluster_discard(bs, offset, count, QCOW2_DISCARD_REQUEST,
|
||||
ret = qcow2_cluster_discard(bs, offset, bytes, QCOW2_DISCARD_REQUEST,
|
||||
false);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
return ret;
|
||||
|
|
|
@ -301,10 +301,10 @@ typedef struct Qcow2COWRegion {
|
|||
* Offset of the COW region in bytes from the start of the first cluster
|
||||
* touched by the request.
|
||||
*/
|
||||
uint64_t offset;
|
||||
unsigned offset;
|
||||
|
||||
/** Number of bytes to copy */
|
||||
int nb_bytes;
|
||||
unsigned nb_bytes;
|
||||
} Qcow2COWRegion;
|
||||
|
||||
/**
|
||||
|
@ -343,6 +343,13 @@ typedef struct QCowL2Meta
|
|||
*/
|
||||
Qcow2COWRegion cow_end;
|
||||
|
||||
/**
|
||||
* The I/O vector with the data from the actual guest write request.
|
||||
* If non-NULL, this is meant to be merged together with the data
|
||||
* from @cow_start and @cow_end into one single write operation.
|
||||
*/
|
||||
QEMUIOVector *data_qiov;
|
||||
|
||||
/** Pointer to next L2Meta of the same write request */
|
||||
struct QCowL2Meta *next;
|
||||
|
||||
|
|
|
@ -61,37 +61,64 @@ static unsigned int qed_count_contiguous_clusters(BDRVQEDState *s,
|
|||
return i - index;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
BDRVQEDState *s;
|
||||
uint64_t pos;
|
||||
size_t len;
|
||||
|
||||
QEDRequest *request;
|
||||
|
||||
/* User callback */
|
||||
QEDFindClusterFunc *cb;
|
||||
void *opaque;
|
||||
} QEDFindClusterCB;
|
||||
|
||||
static void qed_find_cluster_cb(void *opaque, int ret)
|
||||
/**
|
||||
* Find the offset of a data cluster
|
||||
*
|
||||
* @s: QED state
|
||||
* @request: L2 cache entry
|
||||
* @pos: Byte position in device
|
||||
* @len: Number of bytes (may be shortened on return)
|
||||
* @img_offset: Contains offset in the image file on success
|
||||
*
|
||||
* This function translates a position in the block device to an offset in the
|
||||
* image file. The translated offset or unallocated range in the image file is
|
||||
* reported back in *img_offset and *len.
|
||||
*
|
||||
* If the L2 table exists, request->l2_table points to the L2 table cache entry
|
||||
* and the caller must free the reference when they are finished. The cache
|
||||
* entry is exposed in this way to avoid callers having to read the L2 table
|
||||
* again later during request processing. If request->l2_table is non-NULL it
|
||||
* will be unreferenced before taking on the new cache entry.
|
||||
*
|
||||
* On success QED_CLUSTER_FOUND is returned and img_offset/len are a contiguous
|
||||
* range in the image file.
|
||||
*
|
||||
* On failure QED_CLUSTER_L2 or QED_CLUSTER_L1 is returned for missing L2 or L1
|
||||
* table offset, respectively. len is number of contiguous unallocated bytes.
|
||||
*/
|
||||
int coroutine_fn qed_find_cluster(BDRVQEDState *s, QEDRequest *request,
|
||||
uint64_t pos, size_t *len,
|
||||
uint64_t *img_offset)
|
||||
{
|
||||
QEDFindClusterCB *find_cluster_cb = opaque;
|
||||
BDRVQEDState *s = find_cluster_cb->s;
|
||||
QEDRequest *request = find_cluster_cb->request;
|
||||
uint64_t l2_offset;
|
||||
uint64_t offset = 0;
|
||||
size_t len = 0;
|
||||
unsigned int index;
|
||||
unsigned int n;
|
||||
int ret;
|
||||
|
||||
/* Limit length to L2 boundary. Requests are broken up at the L2 boundary
|
||||
* so that a request acts on one L2 table at a time.
|
||||
*/
|
||||
*len = MIN(*len, (((pos >> s->l1_shift) + 1) << s->l1_shift) - pos);
|
||||
|
||||
l2_offset = s->l1_table->offsets[qed_l1_index(s, pos)];
|
||||
if (qed_offset_is_unalloc_cluster(l2_offset)) {
|
||||
*img_offset = 0;
|
||||
return QED_CLUSTER_L1;
|
||||
}
|
||||
if (!qed_check_table_offset(s, l2_offset)) {
|
||||
*img_offset = *len = 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = qed_read_l2_table(s, request, l2_offset);
|
||||
qed_acquire(s);
|
||||
if (ret) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
index = qed_l2_index(s, find_cluster_cb->pos);
|
||||
n = qed_bytes_to_clusters(s,
|
||||
qed_offset_into_cluster(s, find_cluster_cb->pos) +
|
||||
find_cluster_cb->len);
|
||||
index = qed_l2_index(s, pos);
|
||||
n = qed_bytes_to_clusters(s, qed_offset_into_cluster(s, pos) + *len);
|
||||
n = qed_count_contiguous_clusters(s, request->l2_table->table,
|
||||
index, n, &offset);
|
||||
|
||||
|
@ -105,64 +132,11 @@ static void qed_find_cluster_cb(void *opaque, int ret)
|
|||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
len = MIN(find_cluster_cb->len, n * s->header.cluster_size -
|
||||
qed_offset_into_cluster(s, find_cluster_cb->pos));
|
||||
*len = MIN(*len,
|
||||
n * s->header.cluster_size - qed_offset_into_cluster(s, pos));
|
||||
|
||||
out:
|
||||
find_cluster_cb->cb(find_cluster_cb->opaque, ret, offset, len);
|
||||
*img_offset = offset;
|
||||
qed_release(s);
|
||||
g_free(find_cluster_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the offset of a data cluster
|
||||
*
|
||||
* @s: QED state
|
||||
* @request: L2 cache entry
|
||||
* @pos: Byte position in device
|
||||
* @len: Number of bytes
|
||||
* @cb: Completion function
|
||||
* @opaque: User data for completion function
|
||||
*
|
||||
* This function translates a position in the block device to an offset in the
|
||||
* image file. It invokes the cb completion callback to report back the
|
||||
* translated offset or unallocated range in the image file.
|
||||
*
|
||||
* If the L2 table exists, request->l2_table points to the L2 table cache entry
|
||||
* and the caller must free the reference when they are finished. The cache
|
||||
* entry is exposed in this way to avoid callers having to read the L2 table
|
||||
* again later during request processing. If request->l2_table is non-NULL it
|
||||
* will be unreferenced before taking on the new cache entry.
|
||||
*/
|
||||
void qed_find_cluster(BDRVQEDState *s, QEDRequest *request, uint64_t pos,
|
||||
size_t len, QEDFindClusterFunc *cb, void *opaque)
|
||||
{
|
||||
QEDFindClusterCB *find_cluster_cb;
|
||||
uint64_t l2_offset;
|
||||
|
||||
/* Limit length to L2 boundary. Requests are broken up at the L2 boundary
|
||||
* so that a request acts on one L2 table at a time.
|
||||
*/
|
||||
len = MIN(len, (((pos >> s->l1_shift) + 1) << s->l1_shift) - pos);
|
||||
|
||||
l2_offset = s->l1_table->offsets[qed_l1_index(s, pos)];
|
||||
if (qed_offset_is_unalloc_cluster(l2_offset)) {
|
||||
cb(opaque, QED_CLUSTER_L1, 0, len);
|
||||
return;
|
||||
}
|
||||
if (!qed_check_table_offset(s, l2_offset)) {
|
||||
cb(opaque, -EINVAL, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
find_cluster_cb = g_malloc(sizeof(*find_cluster_cb));
|
||||
find_cluster_cb->s = s;
|
||||
find_cluster_cb->pos = pos;
|
||||
find_cluster_cb->len = len;
|
||||
find_cluster_cb->cb = cb;
|
||||
find_cluster_cb->opaque = opaque;
|
||||
find_cluster_cb->request = request;
|
||||
|
||||
qed_read_l2_table(s, request, l2_offset,
|
||||
qed_find_cluster_cb, find_cluster_cb);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* QEMU Enhanced Disk Format
|
||||
*
|
||||
* Copyright IBM, Corp. 2010
|
||||
*
|
||||
* Authors:
|
||||
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
|
||||
* See the COPYING.LIB file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qed.h"
|
||||
|
||||
void *gencb_alloc(size_t len, BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
GenericCB *gencb = g_malloc(len);
|
||||
gencb->cb = cb;
|
||||
gencb->opaque = opaque;
|
||||
return gencb;
|
||||
}
|
||||
|
||||
void gencb_complete(void *opaque, int ret)
|
||||
{
|
||||
GenericCB *gencb = opaque;
|
||||
BlockCompletionFunc *cb = gencb->cb;
|
||||
void *user_opaque = gencb->opaque;
|
||||
|
||||
g_free(gencb);
|
||||
cb(user_opaque, ret);
|
||||
}
|
|
@ -18,99 +18,38 @@
|
|||
#include "qed.h"
|
||||
#include "qemu/bswap.h"
|
||||
|
||||
typedef struct {
|
||||
GenericCB gencb;
|
||||
BDRVQEDState *s;
|
||||
QEDTable *table;
|
||||
|
||||
struct iovec iov;
|
||||
QEMUIOVector qiov;
|
||||
} QEDReadTableCB;
|
||||
|
||||
static void qed_read_table_cb(void *opaque, int ret)
|
||||
static int qed_read_table(BDRVQEDState *s, uint64_t offset, QEDTable *table)
|
||||
{
|
||||
QEDReadTableCB *read_table_cb = opaque;
|
||||
QEDTable *table = read_table_cb->table;
|
||||
BDRVQEDState *s = read_table_cb->s;
|
||||
int noffsets = read_table_cb->qiov.size / sizeof(uint64_t);
|
||||
int i;
|
||||
QEMUIOVector qiov;
|
||||
int noffsets;
|
||||
int i, ret;
|
||||
|
||||
/* Handle I/O error */
|
||||
if (ret) {
|
||||
struct iovec iov = {
|
||||
.iov_base = table->offsets,
|
||||
.iov_len = s->header.cluster_size * s->header.table_size,
|
||||
};
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
||||
trace_qed_read_table(s, offset, table);
|
||||
|
||||
ret = bdrv_preadv(s->bs->file, offset, &qiov);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Byteswap offsets */
|
||||
qed_acquire(s);
|
||||
noffsets = qiov.size / sizeof(uint64_t);
|
||||
for (i = 0; i < noffsets; i++) {
|
||||
table->offsets[i] = le64_to_cpu(table->offsets[i]);
|
||||
}
|
||||
qed_release(s);
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
/* Completion */
|
||||
trace_qed_read_table_cb(s, read_table_cb->table, ret);
|
||||
gencb_complete(&read_table_cb->gencb, ret);
|
||||
}
|
||||
|
||||
static void qed_read_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
QEDReadTableCB *read_table_cb = gencb_alloc(sizeof(*read_table_cb),
|
||||
cb, opaque);
|
||||
QEMUIOVector *qiov = &read_table_cb->qiov;
|
||||
|
||||
trace_qed_read_table(s, offset, table);
|
||||
|
||||
read_table_cb->s = s;
|
||||
read_table_cb->table = table;
|
||||
read_table_cb->iov.iov_base = table->offsets,
|
||||
read_table_cb->iov.iov_len = s->header.cluster_size * s->header.table_size,
|
||||
|
||||
qemu_iovec_init_external(qiov, &read_table_cb->iov, 1);
|
||||
bdrv_aio_readv(s->bs->file, offset / BDRV_SECTOR_SIZE, qiov,
|
||||
qiov->size / BDRV_SECTOR_SIZE,
|
||||
qed_read_table_cb, read_table_cb);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GenericCB gencb;
|
||||
BDRVQEDState *s;
|
||||
QEDTable *orig_table;
|
||||
QEDTable *table;
|
||||
bool flush; /* flush after write? */
|
||||
|
||||
struct iovec iov;
|
||||
QEMUIOVector qiov;
|
||||
} QEDWriteTableCB;
|
||||
|
||||
static void qed_write_table_cb(void *opaque, int ret)
|
||||
{
|
||||
QEDWriteTableCB *write_table_cb = opaque;
|
||||
BDRVQEDState *s = write_table_cb->s;
|
||||
|
||||
trace_qed_write_table_cb(s,
|
||||
write_table_cb->orig_table,
|
||||
write_table_cb->flush,
|
||||
ret);
|
||||
|
||||
if (ret) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_table_cb->flush) {
|
||||
/* We still need to flush first */
|
||||
write_table_cb->flush = false;
|
||||
qed_acquire(s);
|
||||
bdrv_aio_flush(write_table_cb->s->bs, qed_write_table_cb,
|
||||
write_table_cb);
|
||||
qed_release(s);
|
||||
return;
|
||||
}
|
||||
|
||||
out:
|
||||
qemu_vfree(write_table_cb->table);
|
||||
gencb_complete(&write_table_cb->gencb, ret);
|
||||
trace_qed_read_table_cb(s, table, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,17 +61,17 @@ out:
|
|||
* @index: Index of first element
|
||||
* @n: Number of elements
|
||||
* @flush: Whether or not to sync to disk
|
||||
* @cb: Completion function
|
||||
* @opaque: Argument for completion function
|
||||
*/
|
||||
static void qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
||||
unsigned int index, unsigned int n, bool flush,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
static int qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
||||
unsigned int index, unsigned int n, bool flush)
|
||||
{
|
||||
QEDWriteTableCB *write_table_cb;
|
||||
unsigned int sector_mask = BDRV_SECTOR_SIZE / sizeof(uint64_t) - 1;
|
||||
unsigned int start, end, i;
|
||||
QEDTable *new_table;
|
||||
struct iovec iov;
|
||||
QEMUIOVector qiov;
|
||||
size_t len_bytes;
|
||||
int ret;
|
||||
|
||||
trace_qed_write_table(s, offset, table, index, n);
|
||||
|
||||
|
@ -142,157 +81,115 @@ static void qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
|||
|
||||
len_bytes = (end - start) * sizeof(uint64_t);
|
||||
|
||||
write_table_cb = gencb_alloc(sizeof(*write_table_cb), cb, opaque);
|
||||
write_table_cb->s = s;
|
||||
write_table_cb->orig_table = table;
|
||||
write_table_cb->flush = flush;
|
||||
write_table_cb->table = qemu_blockalign(s->bs, len_bytes);
|
||||
write_table_cb->iov.iov_base = write_table_cb->table->offsets;
|
||||
write_table_cb->iov.iov_len = len_bytes;
|
||||
qemu_iovec_init_external(&write_table_cb->qiov, &write_table_cb->iov, 1);
|
||||
new_table = qemu_blockalign(s->bs, len_bytes);
|
||||
iov = (struct iovec) {
|
||||
.iov_base = new_table->offsets,
|
||||
.iov_len = len_bytes,
|
||||
};
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
||||
/* Byteswap table */
|
||||
for (i = start; i < end; i++) {
|
||||
uint64_t le_offset = cpu_to_le64(table->offsets[i]);
|
||||
write_table_cb->table->offsets[i - start] = le_offset;
|
||||
new_table->offsets[i - start] = le_offset;
|
||||
}
|
||||
|
||||
/* Adjust for offset into table */
|
||||
offset += start * sizeof(uint64_t);
|
||||
|
||||
bdrv_aio_writev(s->bs->file, offset / BDRV_SECTOR_SIZE,
|
||||
&write_table_cb->qiov,
|
||||
write_table_cb->qiov.size / BDRV_SECTOR_SIZE,
|
||||
qed_write_table_cb, write_table_cb);
|
||||
ret = bdrv_pwritev(s->bs->file, offset, &qiov);
|
||||
trace_qed_write_table_cb(s, table, flush, ret);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate return value from async callback
|
||||
*/
|
||||
static void qed_sync_cb(void *opaque, int ret)
|
||||
{
|
||||
*(int *)opaque = ret;
|
||||
if (flush) {
|
||||
qed_acquire(s);
|
||||
ret = bdrv_flush(s->bs);
|
||||
qed_release(s);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
qemu_vfree(new_table);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qed_read_l1_table_sync(BDRVQEDState *s)
|
||||
{
|
||||
int ret = -EINPROGRESS;
|
||||
|
||||
qed_read_table(s, s->header.l1_table_offset,
|
||||
s->l1_table, qed_sync_cb, &ret);
|
||||
BDRV_POLL_WHILE(s->bs, ret == -EINPROGRESS);
|
||||
|
||||
return ret;
|
||||
return qed_read_table(s, s->header.l1_table_offset, s->l1_table);
|
||||
}
|
||||
|
||||
void qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
int qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n)
|
||||
{
|
||||
BLKDBG_EVENT(s->bs->file, BLKDBG_L1_UPDATE);
|
||||
qed_write_table(s, s->header.l1_table_offset,
|
||||
s->l1_table, index, n, false, cb, opaque);
|
||||
return qed_write_table(s, s->header.l1_table_offset,
|
||||
s->l1_table, index, n, false);
|
||||
}
|
||||
|
||||
int qed_write_l1_table_sync(BDRVQEDState *s, unsigned int index,
|
||||
unsigned int n)
|
||||
{
|
||||
int ret = -EINPROGRESS;
|
||||
|
||||
qed_write_l1_table(s, index, n, qed_sync_cb, &ret);
|
||||
BDRV_POLL_WHILE(s->bs, ret == -EINPROGRESS);
|
||||
|
||||
return ret;
|
||||
return qed_write_l1_table(s, index, n);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GenericCB gencb;
|
||||
BDRVQEDState *s;
|
||||
uint64_t l2_offset;
|
||||
QEDRequest *request;
|
||||
} QEDReadL2TableCB;
|
||||
|
||||
static void qed_read_l2_table_cb(void *opaque, int ret)
|
||||
int qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset)
|
||||
{
|
||||
QEDReadL2TableCB *read_l2_table_cb = opaque;
|
||||
QEDRequest *request = read_l2_table_cb->request;
|
||||
BDRVQEDState *s = read_l2_table_cb->s;
|
||||
CachedL2Table *l2_table = request->l2_table;
|
||||
uint64_t l2_offset = read_l2_table_cb->l2_offset;
|
||||
|
||||
qed_acquire(s);
|
||||
if (ret) {
|
||||
/* can't trust loaded L2 table anymore */
|
||||
qed_unref_l2_cache_entry(l2_table);
|
||||
request->l2_table = NULL;
|
||||
} else {
|
||||
l2_table->offset = l2_offset;
|
||||
|
||||
qed_commit_l2_cache_entry(&s->l2_cache, l2_table);
|
||||
|
||||
/* This is guaranteed to succeed because we just committed the entry
|
||||
* to the cache.
|
||||
*/
|
||||
request->l2_table = qed_find_l2_cache_entry(&s->l2_cache, l2_offset);
|
||||
assert(request->l2_table != NULL);
|
||||
}
|
||||
qed_release(s);
|
||||
|
||||
gencb_complete(&read_l2_table_cb->gencb, ret);
|
||||
}
|
||||
|
||||
void qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
QEDReadL2TableCB *read_l2_table_cb;
|
||||
int ret;
|
||||
|
||||
qed_unref_l2_cache_entry(request->l2_table);
|
||||
|
||||
/* Check for cached L2 entry */
|
||||
request->l2_table = qed_find_l2_cache_entry(&s->l2_cache, offset);
|
||||
if (request->l2_table) {
|
||||
cb(opaque, 0);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
request->l2_table = qed_alloc_l2_cache_entry(&s->l2_cache);
|
||||
request->l2_table->table = qed_alloc_table(s);
|
||||
|
||||
read_l2_table_cb = gencb_alloc(sizeof(*read_l2_table_cb), cb, opaque);
|
||||
read_l2_table_cb->s = s;
|
||||
read_l2_table_cb->l2_offset = offset;
|
||||
read_l2_table_cb->request = request;
|
||||
|
||||
BLKDBG_EVENT(s->bs->file, BLKDBG_L2_LOAD);
|
||||
qed_read_table(s, offset, request->l2_table->table,
|
||||
qed_read_l2_table_cb, read_l2_table_cb);
|
||||
ret = qed_read_table(s, offset, request->l2_table->table);
|
||||
|
||||
qed_acquire(s);
|
||||
if (ret) {
|
||||
/* can't trust loaded L2 table anymore */
|
||||
qed_unref_l2_cache_entry(request->l2_table);
|
||||
request->l2_table = NULL;
|
||||
} else {
|
||||
request->l2_table->offset = offset;
|
||||
|
||||
qed_commit_l2_cache_entry(&s->l2_cache, request->l2_table);
|
||||
|
||||
/* This is guaranteed to succeed because we just committed the entry
|
||||
* to the cache.
|
||||
*/
|
||||
request->l2_table = qed_find_l2_cache_entry(&s->l2_cache, offset);
|
||||
assert(request->l2_table != NULL);
|
||||
}
|
||||
|
||||
int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request, uint64_t offset)
|
||||
{
|
||||
int ret = -EINPROGRESS;
|
||||
|
||||
qed_read_l2_table(s, request, offset, qed_sync_cb, &ret);
|
||||
BDRV_POLL_WHILE(s->bs, ret == -EINPROGRESS);
|
||||
qed_release(s);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request, uint64_t offset)
|
||||
{
|
||||
return qed_read_l2_table(s, request, offset);
|
||||
}
|
||||
|
||||
int qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush)
|
||||
{
|
||||
BLKDBG_EVENT(s->bs->file, BLKDBG_L2_UPDATE);
|
||||
qed_write_table(s, request->l2_table->offset,
|
||||
request->l2_table->table, index, n, flush, cb, opaque);
|
||||
return qed_write_table(s, request->l2_table->offset,
|
||||
request->l2_table->table, index, n, flush);
|
||||
}
|
||||
|
||||
int qed_write_l2_table_sync(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush)
|
||||
{
|
||||
int ret = -EINPROGRESS;
|
||||
|
||||
qed_write_l2_table(s, request, index, n, flush, qed_sync_cb, &ret);
|
||||
BDRV_POLL_WHILE(s->bs, ret == -EINPROGRESS);
|
||||
|
||||
return ret;
|
||||
return qed_write_l2_table(s, request, index, n, flush);
|
||||
}
|
||||
|
|
753
block/qed.c
753
block/qed.c
File diff suppressed because it is too large
Load Diff
54
block/qed.h
54
block/qed.h
|
@ -129,8 +129,7 @@ enum {
|
|||
};
|
||||
|
||||
typedef struct QEDAIOCB {
|
||||
BlockAIOCB common;
|
||||
int bh_ret; /* final return status for completion bh */
|
||||
BlockDriverState *bs;
|
||||
QSIMPLEQ_ENTRY(QEDAIOCB) next; /* next request */
|
||||
int flags; /* QED_AIOCB_* bits ORed together */
|
||||
uint64_t end_pos; /* request end on block device, in bytes */
|
||||
|
@ -163,7 +162,8 @@ typedef struct {
|
|||
uint32_t l2_mask;
|
||||
|
||||
/* Allocating write request queue */
|
||||
QSIMPLEQ_HEAD(, QEDAIOCB) allocating_write_reqs;
|
||||
QEDAIOCB *allocating_acb;
|
||||
CoQueue allocating_write_reqs;
|
||||
bool allocating_write_reqs_plugged;
|
||||
|
||||
/* Periodic flush and clear need check flag */
|
||||
|
@ -177,41 +177,9 @@ enum {
|
|||
QED_CLUSTER_L1, /* cluster missing in L1 */
|
||||
};
|
||||
|
||||
/**
|
||||
* qed_find_cluster() completion callback
|
||||
*
|
||||
* @opaque: User data for completion callback
|
||||
* @ret: QED_CLUSTER_FOUND Success
|
||||
* QED_CLUSTER_L2 Data cluster unallocated in L2
|
||||
* QED_CLUSTER_L1 L2 unallocated in L1
|
||||
* -errno POSIX error occurred
|
||||
* @offset: Data cluster offset
|
||||
* @len: Contiguous bytes starting from cluster offset
|
||||
*
|
||||
* This function is invoked when qed_find_cluster() completes.
|
||||
*
|
||||
* On success ret is QED_CLUSTER_FOUND and offset/len are a contiguous range
|
||||
* in the image file.
|
||||
*
|
||||
* On failure ret is QED_CLUSTER_L2 or QED_CLUSTER_L1 for missing L2 or L1
|
||||
* table offset, respectively. len is number of contiguous unallocated bytes.
|
||||
*/
|
||||
typedef void QEDFindClusterFunc(void *opaque, int ret, uint64_t offset, size_t len);
|
||||
|
||||
void qed_acquire(BDRVQEDState *s);
|
||||
void qed_release(BDRVQEDState *s);
|
||||
|
||||
/**
|
||||
* Generic callback for chaining async callbacks
|
||||
*/
|
||||
typedef struct {
|
||||
BlockCompletionFunc *cb;
|
||||
void *opaque;
|
||||
} GenericCB;
|
||||
|
||||
void *gencb_alloc(size_t len, BlockCompletionFunc *cb, void *opaque);
|
||||
void gencb_complete(void *opaque, int ret);
|
||||
|
||||
/**
|
||||
* Header functions
|
||||
*/
|
||||
|
@ -231,25 +199,23 @@ void qed_commit_l2_cache_entry(L2TableCache *l2_cache, CachedL2Table *l2_table);
|
|||
* Table I/O functions
|
||||
*/
|
||||
int qed_read_l1_table_sync(BDRVQEDState *s);
|
||||
void qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n);
|
||||
int qed_write_l1_table_sync(BDRVQEDState *s, unsigned int index,
|
||||
unsigned int n);
|
||||
int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request,
|
||||
uint64_t offset);
|
||||
void qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
void qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset);
|
||||
int qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush);
|
||||
int qed_write_l2_table_sync(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush);
|
||||
|
||||
/**
|
||||
* Cluster functions
|
||||
*/
|
||||
void qed_find_cluster(BDRVQEDState *s, QEDRequest *request, uint64_t pos,
|
||||
size_t len, QEDFindClusterFunc *cb, void *opaque);
|
||||
int coroutine_fn qed_find_cluster(BDRVQEDState *s, QEDRequest *request,
|
||||
uint64_t pos, size_t *len,
|
||||
uint64_t *img_offset);
|
||||
|
||||
/**
|
||||
* Consistency check
|
||||
|
|
|
@ -264,7 +264,7 @@ static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs,
|
|||
}
|
||||
|
||||
static int coroutine_fn raw_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int count,
|
||||
int64_t offset, int bytes,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
|
@ -272,18 +272,18 @@ static int coroutine_fn raw_co_pwrite_zeroes(BlockDriverState *bs,
|
|||
return -EINVAL;
|
||||
}
|
||||
offset += s->offset;
|
||||
return bdrv_co_pwrite_zeroes(bs->file, offset, count, flags);
|
||||
return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
|
||||
}
|
||||
|
||||
static int coroutine_fn raw_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int count)
|
||||
int64_t offset, int bytes)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
if (offset > UINT64_MAX - s->offset) {
|
||||
return -EINVAL;
|
||||
}
|
||||
offset += s->offset;
|
||||
return bdrv_co_pdiscard(bs->file->bs, offset, count);
|
||||
return bdrv_co_pdiscard(bs->file->bs, offset, bytes);
|
||||
}
|
||||
|
||||
static int64_t raw_getlength(BlockDriverState *bs)
|
||||
|
|
|
@ -1065,11 +1065,11 @@ static int qemu_rbd_snap_list(BlockDriverState *bs,
|
|||
#ifdef LIBRBD_SUPPORTS_DISCARD
|
||||
static BlockAIOCB *qemu_rbd_aio_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset,
|
||||
int count,
|
||||
int bytes,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
{
|
||||
return rbd_start_aio(bs, offset, NULL, count, cb, opaque,
|
||||
return rbd_start_aio(bs, offset, NULL, bytes, cb, opaque,
|
||||
RBD_AIO_DISCARD);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1046,11 +1046,11 @@ static void sd_parse_uri(SheepdogConfig *cfg, const char *filename,
|
|||
}
|
||||
|
||||
/* transport */
|
||||
if (!strcmp(uri->scheme, "sheepdog")) {
|
||||
if (!g_strcmp0(uri->scheme, "sheepdog")) {
|
||||
is_unix = false;
|
||||
} else if (!strcmp(uri->scheme, "sheepdog+tcp")) {
|
||||
} else if (!g_strcmp0(uri->scheme, "sheepdog+tcp")) {
|
||||
is_unix = false;
|
||||
} else if (!strcmp(uri->scheme, "sheepdog+unix")) {
|
||||
} else if (!g_strcmp0(uri->scheme, "sheepdog+unix")) {
|
||||
is_unix = true;
|
||||
} else {
|
||||
error_setg(&err, "URI scheme must be 'sheepdog', 'sheepdog+tcp',"
|
||||
|
@ -2935,7 +2935,7 @@ static int sd_load_vmstate(BlockDriverState *bs, QEMUIOVector *qiov,
|
|||
|
||||
|
||||
static coroutine_fn int sd_co_pdiscard(BlockDriverState *bs, int64_t offset,
|
||||
int count)
|
||||
int bytes)
|
||||
{
|
||||
SheepdogAIOCB acb;
|
||||
BDRVSheepdogState *s = bs->opaque;
|
||||
|
@ -2953,11 +2953,11 @@ static coroutine_fn int sd_co_pdiscard(BlockDriverState *bs, int64_t offset,
|
|||
iov.iov_len = sizeof(zero);
|
||||
discard_iov.iov = &iov;
|
||||
discard_iov.niov = 1;
|
||||
if (!QEMU_IS_ALIGNED(offset | count, BDRV_SECTOR_SIZE)) {
|
||||
if (!QEMU_IS_ALIGNED(offset | bytes, BDRV_SECTOR_SIZE)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
sd_aio_setup(&acb, s, &discard_iov, offset >> BDRV_SECTOR_BITS,
|
||||
count >> BDRV_SECTOR_BITS, AIOCB_DISCARD_OBJ);
|
||||
bytes >> BDRV_SECTOR_BITS, AIOCB_DISCARD_OBJ);
|
||||
sd_co_rw_vector(&acb);
|
||||
sd_aio_complete(&acb);
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ static int parse_uri(const char *filename, QDict *options, Error **errp)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (strcmp(uri->scheme, "ssh") != 0) {
|
||||
if (g_strcmp0(uri->scheme, "ssh") != 0) {
|
||||
error_setg(errp, "URI scheme must be 'ssh'");
|
||||
goto err;
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
* Again, all this is handled internally and is mostly transparent to
|
||||
* the outside. The 'throttle_timers' field however has an additional
|
||||
* constraint because it may be temporarily invalid (see for example
|
||||
* bdrv_set_aio_context()). Therefore in this file a thread will
|
||||
* blk_set_aio_context()). Therefore in this file a thread will
|
||||
* access some other BlockBackend's timers only after verifying that
|
||||
* that BlockBackend has throttled requests in the queue.
|
||||
*/
|
||||
|
|
|
@ -9,9 +9,6 @@ blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags
|
|||
blk_co_pwritev(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags %x"
|
||||
|
||||
# block/io.c
|
||||
bdrv_aio_flush(void *bs, void *opaque) "bs %p opaque %p"
|
||||
bdrv_aio_readv(void *bs, int64_t sector_num, int nb_sectors, void *opaque) "bs %p sector_num %"PRId64" nb_sectors %d opaque %p"
|
||||
bdrv_aio_writev(void *bs, int64_t sector_num, int nb_sectors, void *opaque) "bs %p sector_num %"PRId64" nb_sectors %d opaque %p"
|
||||
bdrv_co_readv(void *bs, int64_t sector_num, int nb_sector) "bs %p sector_num %"PRId64" nb_sectors %d"
|
||||
bdrv_co_writev(void *bs, int64_t sector_num, int nb_sector) "bs %p sector_num %"PRId64" nb_sectors %d"
|
||||
bdrv_co_pwrite_zeroes(void *bs, int64_t offset, int count, int flags) "bs %p offset %"PRId64" count %d flags %#x"
|
||||
|
|
|
@ -139,7 +139,7 @@ static void block_job_resume(BlockJob *job)
|
|||
block_job_enter(job);
|
||||
}
|
||||
|
||||
static void block_job_ref(BlockJob *job)
|
||||
void block_job_ref(BlockJob *job)
|
||||
{
|
||||
++job->refcnt;
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ static void block_job_attached_aio_context(AioContext *new_context,
|
|||
void *opaque);
|
||||
static void block_job_detach_aio_context(void *opaque);
|
||||
|
||||
static void block_job_unref(BlockJob *job)
|
||||
void block_job_unref(BlockJob *job)
|
||||
{
|
||||
if (--job->refcnt == 0) {
|
||||
BlockDriverState *bs = blk_bs(job->blk);
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
* cmb_size_mb=<cmb_size_mb[optional]>
|
||||
*
|
||||
* Note cmb_size_mb denotes size of CMB in MB. CMB is assumed to be at
|
||||
* offset 0 in BAR2 and supports SQS only for now.
|
||||
* offset 0 in BAR2 and supports only WDS, RDS and SQS for now.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
@ -93,8 +93,8 @@ static void nvme_isr_notify(NvmeCtrl *n, NvmeCQueue *cq)
|
|||
}
|
||||
}
|
||||
|
||||
static uint16_t nvme_map_prp(QEMUSGList *qsg, uint64_t prp1, uint64_t prp2,
|
||||
uint32_t len, NvmeCtrl *n)
|
||||
static uint16_t nvme_map_prp(QEMUSGList *qsg, QEMUIOVector *iov, uint64_t prp1,
|
||||
uint64_t prp2, uint32_t len, NvmeCtrl *n)
|
||||
{
|
||||
hwaddr trans_len = n->page_size - (prp1 % n->page_size);
|
||||
trans_len = MIN(len, trans_len);
|
||||
|
@ -102,10 +102,15 @@ static uint16_t nvme_map_prp(QEMUSGList *qsg, uint64_t prp1, uint64_t prp2,
|
|||
|
||||
if (!prp1) {
|
||||
return NVME_INVALID_FIELD | NVME_DNR;
|
||||
}
|
||||
|
||||
} else if (n->cmbsz && prp1 >= n->ctrl_mem.addr &&
|
||||
prp1 < n->ctrl_mem.addr + int128_get64(n->ctrl_mem.size)) {
|
||||
qsg->nsg = 0;
|
||||
qemu_iovec_init(iov, num_prps);
|
||||
qemu_iovec_add(iov, (void *)&n->cmbuf[prp1 - n->ctrl_mem.addr], trans_len);
|
||||
} else {
|
||||
pci_dma_sglist_init(qsg, &n->parent_obj, num_prps);
|
||||
qemu_sglist_add(qsg, prp1, trans_len);
|
||||
}
|
||||
len -= trans_len;
|
||||
if (len) {
|
||||
if (!prp2) {
|
||||
|
@ -118,7 +123,7 @@ static uint16_t nvme_map_prp(QEMUSGList *qsg, uint64_t prp1, uint64_t prp2,
|
|||
|
||||
nents = (len + n->page_size - 1) >> n->page_bits;
|
||||
prp_trans = MIN(n->max_prp_ents, nents) * sizeof(uint64_t);
|
||||
pci_dma_read(&n->parent_obj, prp2, (void *)prp_list, prp_trans);
|
||||
nvme_addr_read(n, prp2, (void *)prp_list, prp_trans);
|
||||
while (len != 0) {
|
||||
uint64_t prp_ent = le64_to_cpu(prp_list[i]);
|
||||
|
||||
|
@ -130,7 +135,7 @@ static uint16_t nvme_map_prp(QEMUSGList *qsg, uint64_t prp1, uint64_t prp2,
|
|||
i = 0;
|
||||
nents = (len + n->page_size - 1) >> n->page_bits;
|
||||
prp_trans = MIN(n->max_prp_ents, nents) * sizeof(uint64_t);
|
||||
pci_dma_read(&n->parent_obj, prp_ent, (void *)prp_list,
|
||||
nvme_addr_read(n, prp_ent, (void *)prp_list,
|
||||
prp_trans);
|
||||
prp_ent = le64_to_cpu(prp_list[i]);
|
||||
}
|
||||
|
@ -140,7 +145,11 @@ static uint16_t nvme_map_prp(QEMUSGList *qsg, uint64_t prp1, uint64_t prp2,
|
|||
}
|
||||
|
||||
trans_len = MIN(len, n->page_size);
|
||||
if (qsg->nsg){
|
||||
qemu_sglist_add(qsg, prp_ent, trans_len);
|
||||
} else {
|
||||
qemu_iovec_add(iov, (void *)&n->cmbuf[prp_ent - n->ctrl_mem.addr], trans_len);
|
||||
}
|
||||
len -= trans_len;
|
||||
i++;
|
||||
}
|
||||
|
@ -148,7 +157,11 @@ static uint16_t nvme_map_prp(QEMUSGList *qsg, uint64_t prp1, uint64_t prp2,
|
|||
if (prp2 & (n->page_size - 1)) {
|
||||
goto unmap;
|
||||
}
|
||||
if (qsg->nsg) {
|
||||
qemu_sglist_add(qsg, prp2, len);
|
||||
} else {
|
||||
qemu_iovec_add(iov, (void *)&n->cmbuf[prp2 - n->ctrl_mem.addr], trans_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NVME_SUCCESS;
|
||||
|
@ -162,16 +175,24 @@ static uint16_t nvme_dma_read_prp(NvmeCtrl *n, uint8_t *ptr, uint32_t len,
|
|||
uint64_t prp1, uint64_t prp2)
|
||||
{
|
||||
QEMUSGList qsg;
|
||||
QEMUIOVector iov;
|
||||
uint16_t status = NVME_SUCCESS;
|
||||
|
||||
if (nvme_map_prp(&qsg, prp1, prp2, len, n)) {
|
||||
if (nvme_map_prp(&qsg, &iov, prp1, prp2, len, n)) {
|
||||
return NVME_INVALID_FIELD | NVME_DNR;
|
||||
}
|
||||
if (qsg.nsg > 0) {
|
||||
if (dma_buf_read(ptr, len, &qsg)) {
|
||||
qemu_sglist_destroy(&qsg);
|
||||
return NVME_INVALID_FIELD | NVME_DNR;
|
||||
status = NVME_INVALID_FIELD | NVME_DNR;
|
||||
}
|
||||
qemu_sglist_destroy(&qsg);
|
||||
return NVME_SUCCESS;
|
||||
} else {
|
||||
if (qemu_iovec_to_buf(&iov, 0, ptr, len) != len) {
|
||||
status = NVME_INVALID_FIELD | NVME_DNR;
|
||||
}
|
||||
qemu_iovec_destroy(&iov);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static void nvme_post_cqes(void *opaque)
|
||||
|
@ -285,20 +306,27 @@ static uint16_t nvme_rw(NvmeCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd,
|
|||
return NVME_LBA_RANGE | NVME_DNR;
|
||||
}
|
||||
|
||||
if (nvme_map_prp(&req->qsg, prp1, prp2, data_size, n)) {
|
||||
if (nvme_map_prp(&req->qsg, &req->iov, prp1, prp2, data_size, n)) {
|
||||
block_acct_invalid(blk_get_stats(n->conf.blk), acct);
|
||||
return NVME_INVALID_FIELD | NVME_DNR;
|
||||
}
|
||||
|
||||
assert((nlb << data_shift) == req->qsg.size);
|
||||
|
||||
req->has_sg = true;
|
||||
dma_acct_start(n->conf.blk, &req->acct, &req->qsg, acct);
|
||||
if (req->qsg.nsg > 0) {
|
||||
req->has_sg = true;
|
||||
req->aiocb = is_write ?
|
||||
dma_blk_write(n->conf.blk, &req->qsg, data_offset, BDRV_SECTOR_SIZE,
|
||||
nvme_rw_cb, req) :
|
||||
dma_blk_read(n->conf.blk, &req->qsg, data_offset, BDRV_SECTOR_SIZE,
|
||||
nvme_rw_cb, req);
|
||||
} else {
|
||||
req->has_sg = false;
|
||||
req->aiocb = is_write ?
|
||||
blk_aio_pwritev(n->conf.blk, data_offset, &req->iov, 0, nvme_rw_cb,
|
||||
req) :
|
||||
blk_aio_preadv(n->conf.blk, data_offset, &req->iov, 0, nvme_rw_cb,
|
||||
req);
|
||||
}
|
||||
|
||||
return NVME_NO_COMPLETE;
|
||||
}
|
||||
|
@ -987,11 +1015,14 @@ static int nvme_init(PCIDevice *pci_dev)
|
|||
NVME_CMBSZ_SET_SQS(n->bar.cmbsz, 1);
|
||||
NVME_CMBSZ_SET_CQS(n->bar.cmbsz, 0);
|
||||
NVME_CMBSZ_SET_LISTS(n->bar.cmbsz, 0);
|
||||
NVME_CMBSZ_SET_RDS(n->bar.cmbsz, 0);
|
||||
NVME_CMBSZ_SET_WDS(n->bar.cmbsz, 0);
|
||||
NVME_CMBSZ_SET_RDS(n->bar.cmbsz, 1);
|
||||
NVME_CMBSZ_SET_WDS(n->bar.cmbsz, 1);
|
||||
NVME_CMBSZ_SET_SZU(n->bar.cmbsz, 2); /* MBs */
|
||||
NVME_CMBSZ_SET_SZ(n->bar.cmbsz, n->cmb_size_mb);
|
||||
|
||||
n->cmbloc = n->bar.cmbloc;
|
||||
n->cmbsz = n->bar.cmbsz;
|
||||
|
||||
n->cmbuf = g_malloc0(NVME_CMBSZ_GETSIZE(n->bar.cmbsz));
|
||||
memory_region_init_io(&n->ctrl_mem, OBJECT(n), &nvme_cmb_ops, n,
|
||||
"nvme-cmb", NVME_CMBSZ_GETSIZE(n->bar.cmbsz));
|
||||
|
|
|
@ -712,6 +712,7 @@ typedef struct NvmeRequest {
|
|||
NvmeCqe cqe;
|
||||
BlockAcctCookie acct;
|
||||
QEMUSGList qsg;
|
||||
QEMUIOVector iov;
|
||||
QTAILQ_ENTRY(NvmeRequest)entry;
|
||||
} NvmeRequest;
|
||||
|
||||
|
|
|
@ -276,7 +276,7 @@ int bdrv_read(BdrvChild *child, int64_t sector_num,
|
|||
int bdrv_write(BdrvChild *child, int64_t sector_num,
|
||||
const uint8_t *buf, int nb_sectors);
|
||||
int bdrv_pwrite_zeroes(BdrvChild *child, int64_t offset,
|
||||
int count, BdrvRequestFlags flags);
|
||||
int bytes, BdrvRequestFlags flags);
|
||||
int bdrv_make_zero(BdrvChild *child, BdrvRequestFlags flags);
|
||||
int bdrv_pread(BdrvChild *child, int64_t offset, void *buf, int bytes);
|
||||
int bdrv_preadv(BdrvChild *child, int64_t offset, QEMUIOVector *qiov);
|
||||
|
@ -295,7 +295,7 @@ int coroutine_fn bdrv_co_writev(BdrvChild *child, int64_t sector_num,
|
|||
* because it may allocate memory for the entire region.
|
||||
*/
|
||||
int coroutine_fn bdrv_co_pwrite_zeroes(BdrvChild *child, int64_t offset,
|
||||
int count, BdrvRequestFlags flags);
|
||||
int bytes, BdrvRequestFlags flags);
|
||||
BlockDriverState *bdrv_find_backing_image(BlockDriverState *bs,
|
||||
const char *backing_file);
|
||||
int bdrv_get_backing_file_depth(BlockDriverState *bs);
|
||||
|
@ -353,14 +353,6 @@ BlockDriverState *check_to_replace_node(BlockDriverState *parent_bs,
|
|||
const char *node_name, Error **errp);
|
||||
|
||||
/* async block I/O */
|
||||
BlockAIOCB *bdrv_aio_readv(BdrvChild *child, int64_t sector_num,
|
||||
QEMUIOVector *iov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
BlockAIOCB *bdrv_aio_writev(BdrvChild *child, int64_t sector_num,
|
||||
QEMUIOVector *iov, int nb_sectors,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
BlockAIOCB *bdrv_aio_flush(BlockDriverState *bs,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
void bdrv_aio_cancel(BlockAIOCB *acb);
|
||||
void bdrv_aio_cancel_async(BlockAIOCB *acb);
|
||||
|
||||
|
@ -419,8 +411,8 @@ void bdrv_drain_all(void);
|
|||
} \
|
||||
waited_; })
|
||||
|
||||
int bdrv_pdiscard(BlockDriverState *bs, int64_t offset, int count);
|
||||
int bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset, int count);
|
||||
int bdrv_pdiscard(BlockDriverState *bs, int64_t offset, int bytes);
|
||||
int bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes);
|
||||
int bdrv_has_zero_init_1(BlockDriverState *bs);
|
||||
int bdrv_has_zero_init(BlockDriverState *bs);
|
||||
bool bdrv_unallocated_blocks_are_zero(BlockDriverState *bs);
|
||||
|
|
|
@ -142,7 +142,7 @@ struct BlockDriver {
|
|||
BlockAIOCB *(*bdrv_aio_flush)(BlockDriverState *bs,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
BlockAIOCB *(*bdrv_aio_pdiscard)(BlockDriverState *bs,
|
||||
int64_t offset, int count,
|
||||
int64_t offset, int bytes,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
|
||||
int coroutine_fn (*bdrv_co_readv)(BlockDriverState *bs,
|
||||
|
@ -163,9 +163,9 @@ struct BlockDriver {
|
|||
* will be called instead.
|
||||
*/
|
||||
int coroutine_fn (*bdrv_co_pwrite_zeroes)(BlockDriverState *bs,
|
||||
int64_t offset, int count, BdrvRequestFlags flags);
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags);
|
||||
int coroutine_fn (*bdrv_co_pdiscard)(BlockDriverState *bs,
|
||||
int64_t offset, int count);
|
||||
int64_t offset, int bytes);
|
||||
|
||||
/*
|
||||
* Building block for bdrv_block_status[_above]. The driver should
|
||||
|
|
|
@ -320,6 +320,24 @@ void block_job_iostatus_reset(BlockJob *job);
|
|||
*/
|
||||
BlockJobTxn *block_job_txn_new(void);
|
||||
|
||||
/**
|
||||
* block_job_ref:
|
||||
*
|
||||
* Add a reference to BlockJob refcnt, it will be decreased with
|
||||
* block_job_unref, and then be freed if it comes to be the last
|
||||
* reference.
|
||||
*/
|
||||
void block_job_ref(BlockJob *job);
|
||||
|
||||
/**
|
||||
* block_job_unref:
|
||||
*
|
||||
* Release a reference that was previously acquired with block_job_ref
|
||||
* or block_job_create. If it's the last reference to the object, it will be
|
||||
* freed.
|
||||
*/
|
||||
void block_job_unref(BlockJob *job);
|
||||
|
||||
/**
|
||||
* block_job_txn_unref:
|
||||
*
|
||||
|
|
|
@ -130,7 +130,7 @@ BlockBackend *blk_by_dev(void *dev);
|
|||
BlockBackend *blk_by_qdev_id(const char *id, Error **errp);
|
||||
void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops, void *opaque);
|
||||
int blk_pread_unthrottled(BlockBackend *blk, int64_t offset, uint8_t *buf,
|
||||
int count);
|
||||
int bytes);
|
||||
int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset,
|
||||
unsigned int bytes, QEMUIOVector *qiov,
|
||||
BdrvRequestFlags flags);
|
||||
|
@ -138,13 +138,13 @@ int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
|
|||
unsigned int bytes, QEMUIOVector *qiov,
|
||||
BdrvRequestFlags flags);
|
||||
int blk_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
||||
int count, BdrvRequestFlags flags);
|
||||
int bytes, BdrvRequestFlags flags);
|
||||
BlockAIOCB *blk_aio_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
||||
int count, BdrvRequestFlags flags,
|
||||
int bytes, BdrvRequestFlags flags,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int blk_make_zero(BlockBackend *blk, BdrvRequestFlags flags);
|
||||
int blk_pread(BlockBackend *blk, int64_t offset, void *buf, int count);
|
||||
int blk_pwrite(BlockBackend *blk, int64_t offset, const void *buf, int count,
|
||||
int blk_pread(BlockBackend *blk, int64_t offset, void *buf, int bytes);
|
||||
int blk_pwrite(BlockBackend *blk, int64_t offset, const void *buf, int bytes,
|
||||
BdrvRequestFlags flags);
|
||||
int64_t blk_getlength(BlockBackend *blk);
|
||||
void blk_get_geometry(BlockBackend *blk, uint64_t *nb_sectors_ptr);
|
||||
|
@ -157,7 +157,7 @@ BlockAIOCB *blk_aio_pwritev(BlockBackend *blk, int64_t offset,
|
|||
BlockCompletionFunc *cb, void *opaque);
|
||||
BlockAIOCB *blk_aio_flush(BlockBackend *blk,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk, int64_t offset, int count,
|
||||
BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk, int64_t offset, int bytes,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
void blk_aio_cancel(BlockAIOCB *acb);
|
||||
void blk_aio_cancel_async(BlockAIOCB *acb);
|
||||
|
@ -165,7 +165,7 @@ int blk_co_ioctl(BlockBackend *blk, unsigned long int req, void *buf);
|
|||
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf);
|
||||
BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int blk_co_pdiscard(BlockBackend *blk, int64_t offset, int count);
|
||||
int blk_co_pdiscard(BlockBackend *blk, int64_t offset, int bytes);
|
||||
int blk_co_flush(BlockBackend *blk);
|
||||
int blk_flush(BlockBackend *blk);
|
||||
int blk_commit_all(void);
|
||||
|
@ -220,11 +220,11 @@ int blk_get_open_flags_from_root_state(BlockBackend *blk);
|
|||
void *blk_aio_get(const AIOCBInfo *aiocb_info, BlockBackend *blk,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int coroutine_fn blk_co_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
||||
int count, BdrvRequestFlags flags);
|
||||
int bytes, BdrvRequestFlags flags);
|
||||
int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf,
|
||||
int count);
|
||||
int bytes);
|
||||
int blk_truncate(BlockBackend *blk, int64_t offset, Error **errp);
|
||||
int blk_pdiscard(BlockBackend *blk, int64_t offset, int count);
|
||||
int blk_pdiscard(BlockBackend *blk, int64_t offset, int bytes);
|
||||
int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf,
|
||||
int64_t pos, int size);
|
||||
int blk_load_vmstate(BlockBackend *blk, uint8_t *buf, int64_t pos, int size);
|
||||
|
|
|
@ -2107,6 +2107,8 @@ int save_snapshot(const char *name, Error **errp)
|
|||
}
|
||||
vm_stop(RUN_STATE_SAVE_VM);
|
||||
|
||||
bdrv_drain_all_begin();
|
||||
|
||||
aio_context_acquire(aio_context);
|
||||
|
||||
memset(sn, 0, sizeof(*sn));
|
||||
|
@ -2144,6 +2146,14 @@ int save_snapshot(const char *name, Error **errp)
|
|||
goto the_end;
|
||||
}
|
||||
|
||||
/* The bdrv_all_create_snapshot() call that follows acquires the AioContext
|
||||
* for itself. BDRV_POLL_WHILE() does not support nested locking because
|
||||
* it only releases the lock once. Therefore synchronous I/O will deadlock
|
||||
* unless we release the AioContext before bdrv_all_create_snapshot().
|
||||
*/
|
||||
aio_context_release(aio_context);
|
||||
aio_context = NULL;
|
||||
|
||||
ret = bdrv_all_create_snapshot(sn, bs, vm_state_size, &bs);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Error while creating snapshot on '%s'",
|
||||
|
@ -2154,7 +2164,12 @@ int save_snapshot(const char *name, Error **errp)
|
|||
ret = 0;
|
||||
|
||||
the_end:
|
||||
if (aio_context) {
|
||||
aio_context_release(aio_context);
|
||||
}
|
||||
|
||||
bdrv_drain_all_end();
|
||||
|
||||
if (saved_vm_running) {
|
||||
vm_start();
|
||||
}
|
||||
|
@ -2263,20 +2278,21 @@ int load_snapshot(const char *name, Error **errp)
|
|||
}
|
||||
|
||||
/* Flush all IO requests so they don't interfere with the new state. */
|
||||
bdrv_drain_all();
|
||||
bdrv_drain_all_begin();
|
||||
|
||||
ret = bdrv_all_goto_snapshot(name, &bs);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Error %d while activating snapshot '%s' on '%s'",
|
||||
ret, name, bdrv_get_device_name(bs));
|
||||
return ret;
|
||||
goto err_drain;
|
||||
}
|
||||
|
||||
/* restore the VM state */
|
||||
f = qemu_fopen_bdrv(bs_vm_state, 0);
|
||||
if (!f) {
|
||||
error_setg(errp, "Could not open VM state file");
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto err_drain;
|
||||
}
|
||||
|
||||
qemu_system_reset(SHUTDOWN_CAUSE_NONE);
|
||||
|
@ -2284,15 +2300,21 @@ int load_snapshot(const char *name, Error **errp)
|
|||
|
||||
aio_context_acquire(aio_context);
|
||||
ret = qemu_loadvm_state(f);
|
||||
migration_incoming_state_destroy();
|
||||
aio_context_release(aio_context);
|
||||
|
||||
migration_incoming_state_destroy();
|
||||
bdrv_drain_all_end();
|
||||
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Error %d while loading VM state", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_drain:
|
||||
bdrv_drain_all_end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void vmstate_register_ram(MemoryRegion *mr, DeviceState *dev)
|
||||
|
|
27
qemu-img.c
27
qemu-img.c
|
@ -887,23 +887,29 @@ static void common_block_job_cb(void *opaque, int ret)
|
|||
static void run_block_job(BlockJob *job, Error **errp)
|
||||
{
|
||||
AioContext *aio_context = blk_get_aio_context(job->blk);
|
||||
int ret = 0;
|
||||
|
||||
/* FIXME In error cases, the job simply goes away and we access a dangling
|
||||
* pointer below. */
|
||||
aio_context_acquire(aio_context);
|
||||
block_job_ref(job);
|
||||
do {
|
||||
aio_poll(aio_context, true);
|
||||
qemu_progress_print(job->len ?
|
||||
((float)job->offset / job->len * 100.f) : 0.0f, 0);
|
||||
} while (!job->ready);
|
||||
} while (!job->ready && !job->completed);
|
||||
|
||||
block_job_complete_sync(job, errp);
|
||||
if (!job->completed) {
|
||||
ret = block_job_complete_sync(job, errp);
|
||||
} else {
|
||||
ret = job->ret;
|
||||
}
|
||||
block_job_unref(job);
|
||||
aio_context_release(aio_context);
|
||||
|
||||
/* A block job may finish instantaneously without publishing any progress,
|
||||
* so just signal completion here */
|
||||
/* publish completion progress only when success */
|
||||
if (!ret) {
|
||||
qemu_progress_print(100.f, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int img_commit(int argc, char **argv)
|
||||
{
|
||||
|
@ -4249,15 +4255,12 @@ static int img_dd(int argc, char **argv)
|
|||
case 'U':
|
||||
force_share = true;
|
||||
break;
|
||||
case OPTION_OBJECT: {
|
||||
QemuOpts *opts;
|
||||
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||
optarg, true);
|
||||
if (!opts) {
|
||||
case OPTION_OBJECT:
|
||||
if (!qemu_opts_parse_noisily(&qemu_object_opts, optarg, true)) {
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
} break;
|
||||
break;
|
||||
case OPTION_IMAGE_OPTS:
|
||||
image_opts = true;
|
||||
break;
|
||||
|
|
|
@ -451,13 +451,13 @@ fail:
|
|||
}
|
||||
|
||||
static int do_pread(BlockBackend *blk, char *buf, int64_t offset,
|
||||
int64_t count, int64_t *total)
|
||||
int64_t bytes, int64_t *total)
|
||||
{
|
||||
if (count > INT_MAX) {
|
||||
if (bytes > INT_MAX) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
*total = blk_pread(blk, offset, (uint8_t *)buf, count);
|
||||
*total = blk_pread(blk, offset, (uint8_t *)buf, bytes);
|
||||
if (*total < 0) {
|
||||
return *total;
|
||||
}
|
||||
|
@ -465,13 +465,13 @@ static int do_pread(BlockBackend *blk, char *buf, int64_t offset,
|
|||
}
|
||||
|
||||
static int do_pwrite(BlockBackend *blk, char *buf, int64_t offset,
|
||||
int64_t count, int flags, int64_t *total)
|
||||
int64_t bytes, int flags, int64_t *total)
|
||||
{
|
||||
if (count > INT_MAX) {
|
||||
if (bytes > INT_MAX) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
*total = blk_pwrite(blk, offset, (uint8_t *)buf, count, flags);
|
||||
*total = blk_pwrite(blk, offset, (uint8_t *)buf, bytes, flags);
|
||||
if (*total < 0) {
|
||||
return *total;
|
||||
}
|
||||
|
@ -481,7 +481,7 @@ static int do_pwrite(BlockBackend *blk, char *buf, int64_t offset,
|
|||
typedef struct {
|
||||
BlockBackend *blk;
|
||||
int64_t offset;
|
||||
int64_t count;
|
||||
int64_t bytes;
|
||||
int64_t *total;
|
||||
int flags;
|
||||
int ret;
|
||||
|
@ -492,7 +492,7 @@ static void coroutine_fn co_pwrite_zeroes_entry(void *opaque)
|
|||
{
|
||||
CoWriteZeroes *data = opaque;
|
||||
|
||||
data->ret = blk_co_pwrite_zeroes(data->blk, data->offset, data->count,
|
||||
data->ret = blk_co_pwrite_zeroes(data->blk, data->offset, data->bytes,
|
||||
data->flags);
|
||||
data->done = true;
|
||||
if (data->ret < 0) {
|
||||
|
@ -500,23 +500,23 @@ static void coroutine_fn co_pwrite_zeroes_entry(void *opaque)
|
|||
return;
|
||||
}
|
||||
|
||||
*data->total = data->count;
|
||||
*data->total = data->bytes;
|
||||
}
|
||||
|
||||
static int do_co_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
||||
int64_t count, int flags, int64_t *total)
|
||||
int64_t bytes, int flags, int64_t *total)
|
||||
{
|
||||
Coroutine *co;
|
||||
CoWriteZeroes data = {
|
||||
.blk = blk,
|
||||
.offset = offset,
|
||||
.count = count,
|
||||
.bytes = bytes,
|
||||
.total = total,
|
||||
.flags = flags,
|
||||
.done = false,
|
||||
};
|
||||
|
||||
if (count > INT_MAX) {
|
||||
if (bytes > INT_MAX) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
|
@ -533,19 +533,19 @@ static int do_co_pwrite_zeroes(BlockBackend *blk, int64_t offset,
|
|||
}
|
||||
|
||||
static int do_write_compressed(BlockBackend *blk, char *buf, int64_t offset,
|
||||
int64_t count, int64_t *total)
|
||||
int64_t bytes, int64_t *total)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (count >> 9 > BDRV_REQUEST_MAX_SECTORS) {
|
||||
if (bytes >> 9 > BDRV_REQUEST_MAX_SECTORS) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
ret = blk_pwrite_compressed(blk, offset, buf, count);
|
||||
ret = blk_pwrite_compressed(blk, offset, buf, bytes);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
*total = count;
|
||||
*total = bytes;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -1701,7 +1701,7 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
|
|||
struct timeval t1, t2;
|
||||
bool Cflag = false, qflag = false;
|
||||
int c, ret;
|
||||
int64_t offset, count;
|
||||
int64_t offset, bytes;
|
||||
|
||||
while ((c = getopt(argc, argv, "Cq")) != -1) {
|
||||
switch (c) {
|
||||
|
@ -1727,11 +1727,11 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
|
|||
}
|
||||
|
||||
optind++;
|
||||
count = cvtnum(argv[optind]);
|
||||
if (count < 0) {
|
||||
print_cvtnum_err(count, argv[optind]);
|
||||
bytes = cvtnum(argv[optind]);
|
||||
if (bytes < 0) {
|
||||
print_cvtnum_err(bytes, argv[optind]);
|
||||
return 0;
|
||||
} else if (count >> BDRV_SECTOR_BITS > BDRV_REQUEST_MAX_SECTORS) {
|
||||
} else if (bytes >> BDRV_SECTOR_BITS > BDRV_REQUEST_MAX_SECTORS) {
|
||||
printf("length cannot exceed %"PRIu64", given %s\n",
|
||||
(uint64_t)BDRV_REQUEST_MAX_SECTORS << BDRV_SECTOR_BITS,
|
||||
argv[optind]);
|
||||
|
@ -1739,7 +1739,7 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
|
|||
}
|
||||
|
||||
gettimeofday(&t1, NULL);
|
||||
ret = blk_pdiscard(blk, offset, count);
|
||||
ret = blk_pdiscard(blk, offset, bytes);
|
||||
gettimeofday(&t2, NULL);
|
||||
|
||||
if (ret < 0) {
|
||||
|
@ -1750,7 +1750,7 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
|
|||
/* Finally, report back -- -C gives a parsable format */
|
||||
if (!qflag) {
|
||||
t2 = tsub(t2, t1);
|
||||
print_report("discard", &t2, offset, count, count, 1, Cflag);
|
||||
print_report("discard", &t2, offset, bytes, bytes, 1, Cflag);
|
||||
}
|
||||
|
||||
out:
|
||||
|
|
221
qemu-options.hx
221
qemu-options.hx
|
@ -610,6 +610,166 @@ DEF("blockdev", HAS_ARG, QEMU_OPTION_blockdev,
|
|||
" [,read-only=on|off][,detect-zeroes=on|off|unmap]\n"
|
||||
" [,driver specific parameters...]\n"
|
||||
" configure a block backend\n", QEMU_ARCH_ALL)
|
||||
STEXI
|
||||
@item -blockdev @var{option}[,@var{option}[,@var{option}[,...]]]
|
||||
@findex -blockdev
|
||||
|
||||
Define a new block driver node. Some of the options apply to all block drivers,
|
||||
other options are only accepted for a specific block driver. See below for a
|
||||
list of generic options and options for the most common block drivers.
|
||||
|
||||
Options that expect a reference to another node (e.g. @code{file}) can be
|
||||
given in two ways. Either you specify the node name of an already existing node
|
||||
(file=@var{node-name}), or you define a new node inline, adding options
|
||||
for the referenced node after a dot (file.filename=@var{path},file.aio=native).
|
||||
|
||||
A block driver node created with @option{-blockdev} can be used for a guest
|
||||
device by specifying its node name for the @code{drive} property in a
|
||||
@option{-device} argument that defines a block device.
|
||||
|
||||
@table @option
|
||||
@item Valid options for any block driver node:
|
||||
|
||||
@table @code
|
||||
@item driver
|
||||
Specifies the block driver to use for the given node.
|
||||
@item node-name
|
||||
This defines the name of the block driver node by which it will be referenced
|
||||
later. The name must be unique, i.e. it must not match the name of a different
|
||||
block driver node, or (if you use @option{-drive} as well) the ID of a drive.
|
||||
|
||||
If no node name is specified, it is automatically generated. The generated node
|
||||
name is not intended to be predictable and changes between QEMU invocations.
|
||||
For the top level, an explicit node name must be specified.
|
||||
@item read-only
|
||||
Open the node read-only. Guest write attempts will fail.
|
||||
@item cache.direct
|
||||
The host page cache can be avoided with @option{cache.direct=on}. This will
|
||||
attempt to do disk IO directly to the guest's memory. QEMU may still perform an
|
||||
internal copy of the data.
|
||||
@item cache.no-flush
|
||||
In case you don't care about data integrity over host failures, you can use
|
||||
@option{cache.no-flush=on}. This option tells QEMU that it never needs to write
|
||||
any data to the disk but can instead keep things in cache. If anything goes
|
||||
wrong, like your host losing power, the disk storage getting disconnected
|
||||
accidentally, etc. your image will most probably be rendered unusable.
|
||||
@item discard=@var{discard}
|
||||
@var{discard} is one of "ignore" (or "off") or "unmap" (or "on") and controls
|
||||
whether @code{discard} (also known as @code{trim} or @code{unmap}) requests are
|
||||
ignored or passed to the filesystem. Some machine types may not support
|
||||
discard requests.
|
||||
@item detect-zeroes=@var{detect-zeroes}
|
||||
@var{detect-zeroes} is "off", "on" or "unmap" and enables the automatic
|
||||
conversion of plain zero writes by the OS to driver specific optimized
|
||||
zero write commands. You may even choose "unmap" if @var{discard} is set
|
||||
to "unmap" to allow a zero write to be converted to an @code{unmap} operation.
|
||||
@end table
|
||||
|
||||
@item Driver-specific options for @code{file}
|
||||
|
||||
This is the protocol-level block driver for accessing regular files.
|
||||
|
||||
@table @code
|
||||
@item filename
|
||||
The path to the image file in the local filesystem
|
||||
@item aio
|
||||
Specifies the AIO backend (threads/native, default: threads)
|
||||
@end table
|
||||
Example:
|
||||
@example
|
||||
-blockdev driver=file,node-name=disk,filename=disk.img
|
||||
@end example
|
||||
|
||||
@item Driver-specific options for @code{raw}
|
||||
|
||||
This is the image format block driver for raw images. It is usually
|
||||
stacked on top of a protocol level block driver such as @code{file}.
|
||||
|
||||
@table @code
|
||||
@item file
|
||||
Reference to or definition of the data source block driver node
|
||||
(e.g. a @code{file} driver node)
|
||||
@end table
|
||||
Example 1:
|
||||
@example
|
||||
-blockdev driver=file,node-name=disk_file,filename=disk.img
|
||||
-blockdev driver=raw,node-name=disk,file=disk_file
|
||||
@end example
|
||||
Example 2:
|
||||
@example
|
||||
-blockdev driver=raw,node-name=disk,file.driver=file,file.filename=disk.img
|
||||
@end example
|
||||
|
||||
@item Driver-specific options for @code{qcow2}
|
||||
|
||||
This is the image format block driver for qcow2 images. It is usually
|
||||
stacked on top of a protocol level block driver such as @code{file}.
|
||||
|
||||
@table @code
|
||||
@item file
|
||||
Reference to or definition of the data source block driver node
|
||||
(e.g. a @code{file} driver node)
|
||||
|
||||
@item backing
|
||||
Reference to or definition of the backing file block device (default is taken
|
||||
from the image file). It is allowed to pass an empty string here in order to
|
||||
disable the default backing file.
|
||||
|
||||
@item lazy-refcounts
|
||||
Whether to enable the lazy refcounts feature (on/off; default is taken from the
|
||||
image file)
|
||||
|
||||
@item cache-size
|
||||
The maximum total size of the L2 table and refcount block caches in bytes
|
||||
(default: 1048576 bytes or 8 clusters, whichever is larger)
|
||||
|
||||
@item l2-cache-size
|
||||
The maximum size of the L2 table cache in bytes
|
||||
(default: 4/5 of the total cache size)
|
||||
|
||||
@item refcount-cache-size
|
||||
The maximum size of the refcount block cache in bytes
|
||||
(default: 1/5 of the total cache size)
|
||||
|
||||
@item cache-clean-interval
|
||||
Clean unused entries in the L2 and refcount caches. The interval is in seconds.
|
||||
The default value is 0 and it disables this feature.
|
||||
|
||||
@item pass-discard-request
|
||||
Whether discard requests to the qcow2 device should be forwarded to the data
|
||||
source (on/off; default: on if discard=unmap is specified, off otherwise)
|
||||
|
||||
@item pass-discard-snapshot
|
||||
Whether discard requests for the data source should be issued when a snapshot
|
||||
operation (e.g. deleting a snapshot) frees clusters in the qcow2 file (on/off;
|
||||
default: on)
|
||||
|
||||
@item pass-discard-other
|
||||
Whether discard requests for the data source should be issued on other
|
||||
occasions where a cluster gets freed (on/off; default: off)
|
||||
|
||||
@item overlap-check
|
||||
Which overlap checks to perform for writes to the image
|
||||
(none/constant/cached/all; default: cached). For details or finer
|
||||
granularity control refer to the QAPI documentation of @code{blockdev-add}.
|
||||
@end table
|
||||
|
||||
Example 1:
|
||||
@example
|
||||
-blockdev driver=file,node-name=my_file,filename=/tmp/disk.qcow2
|
||||
-blockdev driver=qcow2,node-name=hda,file=my_file,overlap-check=none,cache-size=16777216
|
||||
@end example
|
||||
Example 2:
|
||||
@example
|
||||
-blockdev driver=qcow2,node-name=disk,file.driver=http,file.filename=http://example.com/image.qcow2
|
||||
@end example
|
||||
|
||||
@item Driver-specific options for other drivers
|
||||
Please refer to the QAPI documentation of the @code{blockdev-add} QMP command.
|
||||
|
||||
@end table
|
||||
|
||||
ETEXI
|
||||
|
||||
DEF("drive", HAS_ARG, QEMU_OPTION_drive,
|
||||
"-drive [file=file][,if=type][,bus=n][,unit=m][,media=d][,index=i]\n"
|
||||
|
@ -630,7 +790,12 @@ STEXI
|
|||
@item -drive @var{option}[,@var{option}[,@var{option}[,...]]]
|
||||
@findex -drive
|
||||
|
||||
Define a new drive. Valid options are:
|
||||
Define a new drive. This includes creating a block driver node (the backend) as
|
||||
well as a guest device, and is mostly a shortcut for defining the corresponding
|
||||
@option{-blockdev} and @option{-device} options.
|
||||
|
||||
@option{-drive} accepts all options that are accepted by @option{-blockdev}. In
|
||||
addition, it knows the following options:
|
||||
|
||||
@table @option
|
||||
@item file=@var{file}
|
||||
|
@ -657,11 +822,31 @@ These options have the same definition as they have in @option{-hdachs}.
|
|||
@var{snapshot} is "on" or "off" and controls snapshot mode for the given drive
|
||||
(see @option{-snapshot}).
|
||||
@item cache=@var{cache}
|
||||
@var{cache} is "none", "writeback", "unsafe", "directsync" or "writethrough" and controls how the host cache is used to access block data.
|
||||
@var{cache} is "none", "writeback", "unsafe", "directsync" or "writethrough"
|
||||
and controls how the host cache is used to access block data. This is a
|
||||
shortcut that sets the @option{cache.direct} and @option{cache.no-flush}
|
||||
options (as in @option{-blockdev}), and additionally @option{cache.writeback},
|
||||
which provides a default for the @option{write-cache} option of block guest
|
||||
devices (as in @option{-device}). The modes correspond to the following
|
||||
settings:
|
||||
|
||||
@c Our texi2pod.pl script doesn't support @multitable, so fall back to using
|
||||
@c plain ASCII art (well, UTF-8 art really). This looks okay both in the manpage
|
||||
@c and the HTML output.
|
||||
@example
|
||||
@ │ cache.writeback cache.direct cache.no-flush
|
||||
─────────────┼─────────────────────────────────────────────────
|
||||
writeback │ on off off
|
||||
none │ on on off
|
||||
writethrough │ off off off
|
||||
directsync │ off on off
|
||||
unsafe │ on off on
|
||||
@end example
|
||||
|
||||
The default mode is @option{cache=writeback}.
|
||||
|
||||
@item aio=@var{aio}
|
||||
@var{aio} is "threads", or "native" and selects between pthread based disk I/O and native Linux AIO.
|
||||
@item discard=@var{discard}
|
||||
@var{discard} is one of "ignore" (or "off") or "unmap" (or "on") and controls whether @dfn{discard} (also known as @dfn{trim} or @dfn{unmap}) requests are ignored or passed to the filesystem. Some machine types may not support discard requests.
|
||||
@item format=@var{format}
|
||||
Specify which disk @var{format} will be used rather than detecting
|
||||
the format. Can be used to specify format=raw to avoid interpreting
|
||||
|
@ -676,16 +861,9 @@ Specify which @var{action} to take on write and read errors. Valid actions are:
|
|||
"report" (report the error to the guest), "enospc" (pause QEMU only if the
|
||||
host disk is full; report the error to the guest otherwise).
|
||||
The default setting is @option{werror=enospc} and @option{rerror=report}.
|
||||
@item readonly
|
||||
Open drive @option{file} as read-only. Guest write attempts will fail.
|
||||
@item copy-on-read=@var{copy-on-read}
|
||||
@var{copy-on-read} is "on" or "off" and enables whether to copy read backing
|
||||
file sectors into the image file.
|
||||
@item detect-zeroes=@var{detect-zeroes}
|
||||
@var{detect-zeroes} is "off", "on" or "unmap" and enables the automatic
|
||||
conversion of plain zero writes by the OS to driver specific optimized
|
||||
zero write commands. You may even choose "unmap" if @var{discard} is set
|
||||
to "unmap" to allow a zero write to be converted to an UNMAP operation.
|
||||
@item bps=@var{b},bps_rd=@var{r},bps_wr=@var{w}
|
||||
Specify bandwidth throttling limits in bytes per second, either for all request
|
||||
types or for reads or writes only. Small values can lead to timeouts or hangs
|
||||
|
@ -712,34 +890,19 @@ prevent guests from circumventing throttling limits by using many small disks
|
|||
instead of a single larger disk.
|
||||
@end table
|
||||
|
||||
By default, the @option{cache=writeback} mode is used. It will report data
|
||||
By default, the @option{cache.writeback=on} mode is used. It will report data
|
||||
writes as completed as soon as the data is present in the host page cache.
|
||||
This is safe as long as your guest OS makes sure to correctly flush disk caches
|
||||
where needed. If your guest OS does not handle volatile disk write caches
|
||||
correctly and your host crashes or loses power, then the guest may experience
|
||||
data corruption.
|
||||
|
||||
For such guests, you should consider using @option{cache=writethrough}. This
|
||||
For such guests, you should consider using @option{cache.writeback=off}. This
|
||||
means that the host page cache will be used to read and write data, but write
|
||||
notification will be sent to the guest only after QEMU has made sure to flush
|
||||
each write to the disk. Be aware that this has a major impact on performance.
|
||||
|
||||
The host page cache can be avoided entirely with @option{cache=none}. This will
|
||||
attempt to do disk IO directly to the guest's memory. QEMU may still perform
|
||||
an internal copy of the data. Note that this is considered a writeback mode and
|
||||
the guest OS must handle the disk write cache correctly in order to avoid data
|
||||
corruption on host crashes.
|
||||
|
||||
The host page cache can be avoided while only sending write notifications to
|
||||
the guest when the data has been flushed to the disk using
|
||||
@option{cache=directsync}.
|
||||
|
||||
In case you don't care about data integrity over host failures, use
|
||||
@option{cache=unsafe}. This option tells QEMU that it never needs to write any
|
||||
data to the disk but can instead keep things in cache. If anything goes wrong,
|
||||
like your host losing power, the disk storage getting disconnected accidentally,
|
||||
etc. your image will most probably be rendered unusable. When using
|
||||
the @option{-snapshot} option, unsafe caching is always used.
|
||||
When using the @option{-snapshot} option, unsafe caching is always used.
|
||||
|
||||
Copy-on-read avoids accessing the same backing file sectors repeatedly and is
|
||||
useful when the backing file is over a slow network. By default copy-on-read
|
||||
|
|
|
@ -45,28 +45,41 @@ _supported_os Linux
|
|||
IMGOPTS="compat=1.1"
|
||||
IMG_SIZE=128K
|
||||
|
||||
echo
|
||||
echo "=== Saving and reloading a VM state to/from a qcow2 image ==="
|
||||
echo
|
||||
_make_test_img $IMG_SIZE
|
||||
|
||||
case "$QEMU_DEFAULT_MACHINE" in
|
||||
s390-ccw-virtio)
|
||||
platform_parm="-no-shutdown"
|
||||
hba=virtio-scsi-ccw
|
||||
;;
|
||||
*)
|
||||
platform_parm=""
|
||||
hba=virtio-scsi-pci
|
||||
;;
|
||||
esac
|
||||
|
||||
_qemu()
|
||||
{
|
||||
$QEMU $platform_parm -nographic -monitor stdio -serial none \
|
||||
-drive if=none,id=drive0,file="$TEST_IMG",format="$IMGFMT" \
|
||||
-device $hba,id=hba0 \
|
||||
-device scsi-hd,drive=drive0 \
|
||||
"$@" |\
|
||||
_filter_qemu | _filter_hmp
|
||||
}
|
||||
|
||||
for extra_args in \
|
||||
"" \
|
||||
"-object iothread,id=iothread0 -set device.hba0.iothread=iothread0"; do
|
||||
echo
|
||||
echo "=== Saving and reloading a VM state to/from a qcow2 image ($extra_args) ==="
|
||||
echo
|
||||
|
||||
_make_test_img $IMG_SIZE
|
||||
|
||||
# Give qemu some time to boot before saving the VM state
|
||||
bash -c 'sleep 1; echo -e "savevm 0\nquit"' |\
|
||||
$QEMU $platform_parm -nographic -monitor stdio -serial none -hda "$TEST_IMG" |\
|
||||
_filter_qemu | _filter_hmp
|
||||
bash -c 'sleep 1; echo -e "savevm 0\nquit"' | _qemu $extra_args
|
||||
# Now try to continue from that VM state (this should just work)
|
||||
echo quit |\
|
||||
$QEMU $platform_parm -nographic -monitor stdio -serial none -hda "$TEST_IMG" -loadvm 0 |\
|
||||
_filter_qemu | _filter_hmp
|
||||
echo quit | _qemu $extra_args -loadvm 0
|
||||
done
|
||||
|
||||
# success, all done
|
||||
echo "*** done"
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
QA output created by 068
|
||||
|
||||
=== Saving and reloading a VM state to/from a qcow2 image ===
|
||||
=== Saving and reloading a VM state to/from a qcow2 image () ===
|
||||
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=131072
|
||||
QEMU X.Y.Z monitor - type 'help' for more information
|
||||
(qemu) savevm 0
|
||||
(qemu) quit
|
||||
QEMU X.Y.Z monitor - type 'help' for more information
|
||||
(qemu) quit
|
||||
|
||||
=== Saving and reloading a VM state to/from a qcow2 image (-object iothread,id=iothread0 -set device.hba0.iothread=iothread0) ===
|
||||
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=131072
|
||||
QEMU X.Y.Z monitor - type 'help' for more information
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Test exiting qemu while jobs are still running
|
||||
#
|
||||
# Copyright (C) 2017 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=kwolf@redhat.com
|
||||
|
||||
seq=`basename $0`
|
||||
echo "QA output created by $seq"
|
||||
|
||||
here=`pwd`
|
||||
status=1 # failure is the default!
|
||||
|
||||
MIG_SOCKET="${TEST_DIR}/migrate"
|
||||
|
||||
_cleanup()
|
||||
{
|
||||
rm -f "${TEST_IMG}.mid"
|
||||
rm -f "${TEST_IMG}.copy"
|
||||
_cleanup_test_img
|
||||
_cleanup_qemu
|
||||
}
|
||||
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||
|
||||
# get standard environment, filters and checks
|
||||
. ./common.rc
|
||||
. ./common.filter
|
||||
. ./common.qemu
|
||||
|
||||
_supported_fmt qcow2
|
||||
_supported_proto file
|
||||
_supported_os Linux
|
||||
|
||||
size=64M
|
||||
TEST_IMG="${TEST_IMG}.base" _make_test_img $size
|
||||
|
||||
echo
|
||||
echo === Starting VM ===
|
||||
echo
|
||||
|
||||
qemu_comm_method="qmp"
|
||||
|
||||
_launch_qemu \
|
||||
-drive file="${TEST_IMG}.base",cache=$CACHEMODE,driver=$IMGFMT,id=disk
|
||||
h=$QEMU_HANDLE
|
||||
_send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" 'return'
|
||||
|
||||
echo
|
||||
echo === Creating backing chain ===
|
||||
echo
|
||||
|
||||
_send_qemu_cmd $h \
|
||||
"{ 'execute': 'blockdev-snapshot-sync',
|
||||
'arguments': { 'device': 'disk',
|
||||
'snapshot-file': '$TEST_IMG.mid',
|
||||
'format': '$IMGFMT',
|
||||
'mode': 'absolute-paths' } }" \
|
||||
"return"
|
||||
|
||||
_send_qemu_cmd $h \
|
||||
"{ 'execute': 'human-monitor-command',
|
||||
'arguments': { 'command-line':
|
||||
'qemu-io disk \"write 0 4M\"' } }" \
|
||||
"return"
|
||||
|
||||
_send_qemu_cmd $h \
|
||||
"{ 'execute': 'blockdev-snapshot-sync',
|
||||
'arguments': { 'device': 'disk',
|
||||
'snapshot-file': '$TEST_IMG',
|
||||
'format': '$IMGFMT',
|
||||
'mode': 'absolute-paths' } }" \
|
||||
"return"
|
||||
|
||||
echo
|
||||
echo === Start commit job and exit qemu ===
|
||||
echo
|
||||
|
||||
# Note that the reference output intentionally includes the 'offset' field in
|
||||
# BLOCK_JOB_CANCELLED events for all of the following block jobs. They are
|
||||
# predictable and any change in the offsets would hint at a bug in the job
|
||||
# throttling code.
|
||||
#
|
||||
# In order to achieve these predictable offsets, all of the following tests
|
||||
# use speed=65536. Each job will perform exactly one iteration before it has
|
||||
# to sleep at least for a second, which is plenty of time for the 'quit' QMP
|
||||
# command to be received (after receiving the command, the rest runs
|
||||
# synchronously, so jobs can arbitrarily continue or complete).
|
||||
#
|
||||
# The buffer size for commit and streaming is 512k (waiting for 8 seconds after
|
||||
# the first request), for active commit and mirror it's large enough to cover
|
||||
# the full 4M, and for backup it's the qcow2 cluster size, which we know is
|
||||
# 64k. As all of these are at least as large as the speed, we are sure that the
|
||||
# offset doesn't advance after the first iteration before qemu exits.
|
||||
|
||||
_send_qemu_cmd $h \
|
||||
"{ 'execute': 'block-commit',
|
||||
'arguments': { 'device': 'disk',
|
||||
'base':'$TEST_IMG.base',
|
||||
'top': '$TEST_IMG.mid',
|
||||
'speed': 65536 } }" \
|
||||
"return"
|
||||
|
||||
_send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
|
||||
wait=1 _cleanup_qemu
|
||||
|
||||
echo
|
||||
echo === Start active commit job and exit qemu ===
|
||||
echo
|
||||
|
||||
_launch_qemu \
|
||||
-drive file="${TEST_IMG}",cache=$CACHEMODE,driver=$IMGFMT,id=disk
|
||||
h=$QEMU_HANDLE
|
||||
_send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" 'return'
|
||||
|
||||
_send_qemu_cmd $h \
|
||||
"{ 'execute': 'block-commit',
|
||||
'arguments': { 'device': 'disk',
|
||||
'base':'$TEST_IMG.base',
|
||||
'speed': 65536 } }" \
|
||||
"return"
|
||||
|
||||
_send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
|
||||
wait=1 _cleanup_qemu
|
||||
|
||||
echo
|
||||
echo === Start mirror job and exit qemu ===
|
||||
echo
|
||||
|
||||
_launch_qemu \
|
||||
-drive file="${TEST_IMG}",cache=$CACHEMODE,driver=$IMGFMT,id=disk
|
||||
h=$QEMU_HANDLE
|
||||
_send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" 'return'
|
||||
|
||||
_send_qemu_cmd $h \
|
||||
"{ 'execute': 'drive-mirror',
|
||||
'arguments': { 'device': 'disk',
|
||||
'target': '$TEST_IMG.copy',
|
||||
'format': '$IMGFMT',
|
||||
'sync': 'full',
|
||||
'speed': 65536 } }" \
|
||||
"return"
|
||||
|
||||
_send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
|
||||
wait=1 _cleanup_qemu
|
||||
|
||||
echo
|
||||
echo === Start backup job and exit qemu ===
|
||||
echo
|
||||
|
||||
_launch_qemu \
|
||||
-drive file="${TEST_IMG}",cache=$CACHEMODE,driver=$IMGFMT,id=disk
|
||||
h=$QEMU_HANDLE
|
||||
_send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" 'return'
|
||||
|
||||
_send_qemu_cmd $h \
|
||||
"{ 'execute': 'drive-backup',
|
||||
'arguments': { 'device': 'disk',
|
||||
'target': '$TEST_IMG.copy',
|
||||
'format': '$IMGFMT',
|
||||
'sync': 'full',
|
||||
'speed': 65536 } }" \
|
||||
"return"
|
||||
|
||||
_send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
|
||||
wait=1 _cleanup_qemu
|
||||
|
||||
echo
|
||||
echo === Start streaming job and exit qemu ===
|
||||
echo
|
||||
|
||||
_launch_qemu \
|
||||
-drive file="${TEST_IMG}",cache=$CACHEMODE,driver=$IMGFMT,id=disk
|
||||
h=$QEMU_HANDLE
|
||||
_send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" 'return'
|
||||
|
||||
_send_qemu_cmd $h \
|
||||
"{ 'execute': 'block-stream',
|
||||
'arguments': { 'device': 'disk',
|
||||
'speed': 65536 } }" \
|
||||
"return"
|
||||
|
||||
_send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
|
||||
wait=1 _cleanup_qemu
|
||||
|
||||
_check_test_img
|
||||
|
||||
# success, all done
|
||||
echo "*** done"
|
||||
rm -f $seq.full
|
||||
status=0
|
|
@ -0,0 +1,59 @@
|
|||
QA output created by 185
|
||||
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
|
||||
|
||||
=== Starting VM ===
|
||||
|
||||
{"return": {}}
|
||||
|
||||
=== Creating backing chain ===
|
||||
|
||||
Formatting 'TEST_DIR/t.qcow2.mid', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.qcow2.base backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
|
||||
{"return": {}}
|
||||
wrote 4194304/4194304 bytes at offset 0
|
||||
4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
{"return": ""}
|
||||
Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.qcow2.mid backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
|
||||
{"return": {}}
|
||||
|
||||
=== Start commit job and exit qemu ===
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "disk", "len": 67108864, "offset": 524288, "speed": 65536, "type": "commit"}}
|
||||
|
||||
=== Start active commit job and exit qemu ===
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "disk", "len": 4194304, "offset": 4194304, "speed": 65536, "type": "commit"}}
|
||||
|
||||
=== Start mirror job and exit qemu ===
|
||||
|
||||
{"return": {}}
|
||||
Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "disk", "len": 4194304, "offset": 4194304, "speed": 65536, "type": "mirror"}}
|
||||
|
||||
=== Start backup job and exit qemu ===
|
||||
|
||||
{"return": {}}
|
||||
Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "disk", "len": 67108864, "offset": 65536, "speed": 65536, "type": "backup"}}
|
||||
|
||||
=== Start streaming job and exit qemu ===
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "disk", "len": 67108864, "offset": 524288, "speed": 65536, "type": "stream"}}
|
||||
No errors were found on the image.
|
||||
*** done
|
|
@ -222,5 +222,8 @@ function _cleanup_qemu()
|
|||
rm -f "${QEMU_FIFO_IN}_${i}" "${QEMU_FIFO_OUT}_${i}"
|
||||
eval "exec ${QEMU_IN[$i]}<&-" # close file descriptors
|
||||
eval "exec ${QEMU_OUT[$i]}<&-"
|
||||
|
||||
unset QEMU_IN[$i]
|
||||
unset QEMU_OUT[$i]
|
||||
done
|
||||
}
|
||||
|
|
|
@ -175,3 +175,4 @@
|
|||
181 rw auto migration
|
||||
182 rw auto quick
|
||||
183 rw auto migration
|
||||
185 rw auto
|
||||
|
|
Loading…
Reference in New Issue