-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1 iQEcBAABAgAGBQJUIAsHAAoJEJykq7OBq3PIyR4H/ikrs35Boxv08mR8rTyfpSnQ ERQhMnKWIe3cy5pzNG5TKiPliljF0FnkNC3KmBLU5TsoqXeW76WJF//Db5hNnTzG FAIeJu2RUSqhjqoz5K6TNYOJGH2XQ+/EZbMyrIeLBwYFn0gFMvZJOVYgpBWP0QTQ 7sImrlxihUalwwL/6twfE6s5aA12DXN8hlC57u+9nvf+5ocaDyOJ7jUBU2EEhSNr TzDTO3gTCSEmnDriwKi3m3mIW/y7kLTXWyGolprZ0UpRyYRSmjcfgBHu8l0X8NCv lKkrYuE4V3QIzJk4BWbZQSPjoDlLbH9gnq3H+VwMxMZDxDKtAqAJRw/3H4yWhZ8= =JJEF -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/stefanha/tags/block-pull-request' into staging # gpg: Signature made Mon 22 Sep 2014 12:41:59 BST using RSA key ID 81AB73C8 # gpg: Good signature from "Stefan Hajnoczi <stefanha@redhat.com>" # gpg: aka "Stefan Hajnoczi <stefanha@gmail.com>" * remotes/stefanha/tags/block-pull-request: (59 commits) block: Always compile virtio-blk dataplane vring: Better error handling if num is too large virtio: Import virtio_vring.h async: aio_context_new(): Handle event_notifier_init failure block: vhdx - fix reading beyond pointer during image creation block: delete cow block driver block/archipelago: Fix typo in qemu_archipelago_truncate() ahci: Add test_identify case to ahci-test. ahci: Add test_hba_enable to ahci-test. ahci: Add test_hba_spec to ahci-test. ahci: properly shadow the TFD register ahci: add test_pci_enable to ahci-test. ahci: Add test_pci_spec to ahci-test. ahci: MSI capability should be at 0x80, not 0x50. ahci: Adding basic functionality qtest. layout: Add generators for refcount table and blocks fuzz: Add fuzzing functions for entries of refcount table and blocks docs: List all image elements currently supported by the fuzzer qapi/block-core: Add "new" qcow2 options qcow2: Add overlap-check.template option ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
380f649e02
@ -283,9 +283,9 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
int count;
|
int count;
|
||||||
int timeout;
|
int timeout;
|
||||||
|
|
||||||
if (aio_prepare(ctx)) {
|
have_select_revents = aio_prepare(ctx);
|
||||||
|
if (have_select_revents) {
|
||||||
blocking = false;
|
blocking = false;
|
||||||
have_select_revents = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
was_dispatching = ctx->dispatching;
|
was_dispatching = ctx->dispatching;
|
||||||
@ -335,6 +335,7 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
event = NULL;
|
event = NULL;
|
||||||
if ((DWORD) (ret - WAIT_OBJECT_0) < count) {
|
if ((DWORD) (ret - WAIT_OBJECT_0) < count) {
|
||||||
event = events[ret - WAIT_OBJECT_0];
|
event = events[ret - WAIT_OBJECT_0];
|
||||||
|
events[ret - WAIT_OBJECT_0] = events[--count];
|
||||||
} else if (!have_select_revents) {
|
} else if (!have_select_revents) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -343,9 +344,6 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
blocking = false;
|
blocking = false;
|
||||||
|
|
||||||
progress |= aio_dispatch_handlers(ctx, event);
|
progress |= aio_dispatch_handlers(ctx, event);
|
||||||
|
|
||||||
/* Try again, but only call each handler once. */
|
|
||||||
events[ret - WAIT_OBJECT_0] = events[--count];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||||
|
16
async.c
16
async.c
@ -289,18 +289,24 @@ static void aio_rfifolock_cb(void *opaque)
|
|||||||
aio_notify(opaque);
|
aio_notify(opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
AioContext *aio_context_new(void)
|
AioContext *aio_context_new(Error **errp)
|
||||||
{
|
{
|
||||||
|
int ret;
|
||||||
AioContext *ctx;
|
AioContext *ctx;
|
||||||
ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
|
ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
|
||||||
|
ret = event_notifier_init(&ctx->notifier, false);
|
||||||
|
if (ret < 0) {
|
||||||
|
g_source_destroy(&ctx->source);
|
||||||
|
error_setg_errno(errp, -ret, "Failed to initialize event notifier");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
aio_set_event_notifier(ctx, &ctx->notifier,
|
||||||
|
(EventNotifierHandler *)
|
||||||
|
event_notifier_test_and_clear);
|
||||||
ctx->pollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
|
ctx->pollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
|
||||||
ctx->thread_pool = NULL;
|
ctx->thread_pool = NULL;
|
||||||
qemu_mutex_init(&ctx->bh_lock);
|
qemu_mutex_init(&ctx->bh_lock);
|
||||||
rfifolock_init(&ctx->lock, aio_rfifolock_cb, ctx);
|
rfifolock_init(&ctx->lock, aio_rfifolock_cb, ctx);
|
||||||
event_notifier_init(&ctx->notifier, false);
|
|
||||||
aio_set_event_notifier(ctx, &ctx->notifier,
|
|
||||||
(EventNotifierHandler *)
|
|
||||||
event_notifier_test_and_clear);
|
|
||||||
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
|
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
|
72
block.c
72
block.c
@ -4640,7 +4640,28 @@ int bdrv_aio_multiwrite(BlockDriverState *bs, BlockRequest *reqs, int num_reqs)
|
|||||||
|
|
||||||
void bdrv_aio_cancel(BlockDriverAIOCB *acb)
|
void bdrv_aio_cancel(BlockDriverAIOCB *acb)
|
||||||
{
|
{
|
||||||
acb->aiocb_info->cancel(acb);
|
qemu_aio_ref(acb);
|
||||||
|
bdrv_aio_cancel_async(acb);
|
||||||
|
while (acb->refcnt > 1) {
|
||||||
|
if (acb->aiocb_info->get_aio_context) {
|
||||||
|
aio_poll(acb->aiocb_info->get_aio_context(acb), true);
|
||||||
|
} else if (acb->bs) {
|
||||||
|
aio_poll(bdrv_get_aio_context(acb->bs), true);
|
||||||
|
} else {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qemu_aio_unref(acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Async version of aio cancel. The caller is not blocked if the acb implements
|
||||||
|
* cancel_async, otherwise we do nothing and let the request normally complete.
|
||||||
|
* In either case the completion callback must be called. */
|
||||||
|
void bdrv_aio_cancel_async(BlockDriverAIOCB *acb)
|
||||||
|
{
|
||||||
|
if (acb->aiocb_info->cancel_async) {
|
||||||
|
acb->aiocb_info->cancel_async(acb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************/
|
/**************************************************************/
|
||||||
@ -4656,18 +4677,8 @@ typedef struct BlockDriverAIOCBSync {
|
|||||||
int is_write;
|
int is_write;
|
||||||
} BlockDriverAIOCBSync;
|
} BlockDriverAIOCBSync;
|
||||||
|
|
||||||
static void bdrv_aio_cancel_em(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
BlockDriverAIOCBSync *acb =
|
|
||||||
container_of(blockacb, BlockDriverAIOCBSync, common);
|
|
||||||
qemu_bh_delete(acb->bh);
|
|
||||||
acb->bh = NULL;
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const AIOCBInfo bdrv_em_aiocb_info = {
|
static const AIOCBInfo bdrv_em_aiocb_info = {
|
||||||
.aiocb_size = sizeof(BlockDriverAIOCBSync),
|
.aiocb_size = sizeof(BlockDriverAIOCBSync),
|
||||||
.cancel = bdrv_aio_cancel_em,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_aio_bh_cb(void *opaque)
|
static void bdrv_aio_bh_cb(void *opaque)
|
||||||
@ -4681,7 +4692,7 @@ static void bdrv_aio_bh_cb(void *opaque)
|
|||||||
acb->common.cb(acb->common.opaque, acb->ret);
|
acb->common.cb(acb->common.opaque, acb->ret);
|
||||||
qemu_bh_delete(acb->bh);
|
qemu_bh_delete(acb->bh);
|
||||||
acb->bh = NULL;
|
acb->bh = NULL;
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockDriverAIOCB *bdrv_aio_rw_vector(BlockDriverState *bs,
|
static BlockDriverAIOCB *bdrv_aio_rw_vector(BlockDriverState *bs,
|
||||||
@ -4738,22 +4749,8 @@ typedef struct BlockDriverAIOCBCoroutine {
|
|||||||
QEMUBH* bh;
|
QEMUBH* bh;
|
||||||
} BlockDriverAIOCBCoroutine;
|
} BlockDriverAIOCBCoroutine;
|
||||||
|
|
||||||
static void bdrv_aio_co_cancel_em(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
AioContext *aio_context = bdrv_get_aio_context(blockacb->bs);
|
|
||||||
BlockDriverAIOCBCoroutine *acb =
|
|
||||||
container_of(blockacb, BlockDriverAIOCBCoroutine, common);
|
|
||||||
bool done = false;
|
|
||||||
|
|
||||||
acb->done = &done;
|
|
||||||
while (!done) {
|
|
||||||
aio_poll(aio_context, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const AIOCBInfo bdrv_em_co_aiocb_info = {
|
static const AIOCBInfo bdrv_em_co_aiocb_info = {
|
||||||
.aiocb_size = sizeof(BlockDriverAIOCBCoroutine),
|
.aiocb_size = sizeof(BlockDriverAIOCBCoroutine),
|
||||||
.cancel = bdrv_aio_co_cancel_em,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_co_em_bh(void *opaque)
|
static void bdrv_co_em_bh(void *opaque)
|
||||||
@ -4762,12 +4759,8 @@ static void bdrv_co_em_bh(void *opaque)
|
|||||||
|
|
||||||
acb->common.cb(acb->common.opaque, acb->req.error);
|
acb->common.cb(acb->common.opaque, acb->req.error);
|
||||||
|
|
||||||
if (acb->done) {
|
|
||||||
*acb->done = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_bh_delete(acb->bh);
|
qemu_bh_delete(acb->bh);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Invoke bdrv_co_do_readv/bdrv_co_do_writev */
|
/* Invoke bdrv_co_do_readv/bdrv_co_do_writev */
|
||||||
@ -4806,7 +4799,6 @@ static BlockDriverAIOCB *bdrv_co_aio_rw_vector(BlockDriverState *bs,
|
|||||||
acb->req.qiov = qiov;
|
acb->req.qiov = qiov;
|
||||||
acb->req.flags = flags;
|
acb->req.flags = flags;
|
||||||
acb->is_write = is_write;
|
acb->is_write = is_write;
|
||||||
acb->done = NULL;
|
|
||||||
|
|
||||||
co = qemu_coroutine_create(bdrv_co_do_rw);
|
co = qemu_coroutine_create(bdrv_co_do_rw);
|
||||||
qemu_coroutine_enter(co, acb);
|
qemu_coroutine_enter(co, acb);
|
||||||
@ -4833,7 +4825,6 @@ BlockDriverAIOCB *bdrv_aio_flush(BlockDriverState *bs,
|
|||||||
BlockDriverAIOCBCoroutine *acb;
|
BlockDriverAIOCBCoroutine *acb;
|
||||||
|
|
||||||
acb = qemu_aio_get(&bdrv_em_co_aiocb_info, bs, cb, opaque);
|
acb = qemu_aio_get(&bdrv_em_co_aiocb_info, bs, cb, opaque);
|
||||||
acb->done = NULL;
|
|
||||||
|
|
||||||
co = qemu_coroutine_create(bdrv_aio_flush_co_entry);
|
co = qemu_coroutine_create(bdrv_aio_flush_co_entry);
|
||||||
qemu_coroutine_enter(co, acb);
|
qemu_coroutine_enter(co, acb);
|
||||||
@ -4863,7 +4854,6 @@ BlockDriverAIOCB *bdrv_aio_discard(BlockDriverState *bs,
|
|||||||
acb = qemu_aio_get(&bdrv_em_co_aiocb_info, bs, cb, opaque);
|
acb = qemu_aio_get(&bdrv_em_co_aiocb_info, bs, cb, opaque);
|
||||||
acb->req.sector = sector_num;
|
acb->req.sector = sector_num;
|
||||||
acb->req.nb_sectors = nb_sectors;
|
acb->req.nb_sectors = nb_sectors;
|
||||||
acb->done = NULL;
|
|
||||||
co = qemu_coroutine_create(bdrv_aio_discard_co_entry);
|
co = qemu_coroutine_create(bdrv_aio_discard_co_entry);
|
||||||
qemu_coroutine_enter(co, acb);
|
qemu_coroutine_enter(co, acb);
|
||||||
|
|
||||||
@ -4891,13 +4881,23 @@ void *qemu_aio_get(const AIOCBInfo *aiocb_info, BlockDriverState *bs,
|
|||||||
acb->bs = bs;
|
acb->bs = bs;
|
||||||
acb->cb = cb;
|
acb->cb = cb;
|
||||||
acb->opaque = opaque;
|
acb->opaque = opaque;
|
||||||
|
acb->refcnt = 1;
|
||||||
return acb;
|
return acb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void qemu_aio_release(void *p)
|
void qemu_aio_ref(void *p)
|
||||||
{
|
{
|
||||||
BlockDriverAIOCB *acb = p;
|
BlockDriverAIOCB *acb = p;
|
||||||
g_slice_free1(acb->aiocb_info->aiocb_size, acb);
|
acb->refcnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qemu_aio_unref(void *p)
|
||||||
|
{
|
||||||
|
BlockDriverAIOCB *acb = p;
|
||||||
|
assert(acb->refcnt > 0);
|
||||||
|
if (--acb->refcnt == 0) {
|
||||||
|
g_slice_free1(acb->aiocb_info->aiocb_size, acb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************/
|
/**************************************************************/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
block-obj-y += raw_bsd.o cow.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o vvfat.o
|
block-obj-y += raw_bsd.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o vvfat.o
|
||||||
block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.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-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
|
||||||
block-obj-y += qed-check.o
|
block-obj-y += qed-check.o
|
||||||
@ -9,6 +9,7 @@ block-obj-y += snapshot.o qapi.o
|
|||||||
block-obj-$(CONFIG_WIN32) += raw-win32.o win32-aio.o
|
block-obj-$(CONFIG_WIN32) += raw-win32.o win32-aio.o
|
||||||
block-obj-$(CONFIG_POSIX) += raw-posix.o
|
block-obj-$(CONFIG_POSIX) += raw-posix.o
|
||||||
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
|
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
|
||||||
|
block-obj-y += null.o
|
||||||
|
|
||||||
block-obj-y += nbd.o nbd-client.o sheepdog.o
|
block-obj-y += nbd.o nbd-client.o sheepdog.o
|
||||||
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
|
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
|
||||||
|
@ -91,7 +91,6 @@ typedef struct ArchipelagoAIOCB {
|
|||||||
struct BDRVArchipelagoState *s;
|
struct BDRVArchipelagoState *s;
|
||||||
QEMUIOVector *qiov;
|
QEMUIOVector *qiov;
|
||||||
ARCHIPCmd cmd;
|
ARCHIPCmd cmd;
|
||||||
bool cancelled;
|
|
||||||
int status;
|
int status;
|
||||||
int64_t size;
|
int64_t size;
|
||||||
int64_t ret;
|
int64_t ret;
|
||||||
@ -318,9 +317,7 @@ static void qemu_archipelago_complete_aio(void *opaque)
|
|||||||
aio_cb->common.cb(aio_cb->common.opaque, aio_cb->ret);
|
aio_cb->common.cb(aio_cb->common.opaque, aio_cb->ret);
|
||||||
aio_cb->status = 0;
|
aio_cb->status = 0;
|
||||||
|
|
||||||
if (!aio_cb->cancelled) {
|
qemu_aio_unref(aio_cb);
|
||||||
qemu_aio_release(aio_cb);
|
|
||||||
}
|
|
||||||
g_free(reqdata);
|
g_free(reqdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -725,19 +722,8 @@ static int qemu_archipelago_create(const char *filename,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qemu_archipelago_aio_cancel(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
ArchipelagoAIOCB *aio_cb = (ArchipelagoAIOCB *) blockacb;
|
|
||||||
aio_cb->cancelled = true;
|
|
||||||
while (aio_cb->status == -EINPROGRESS) {
|
|
||||||
aio_poll(bdrv_get_aio_context(aio_cb->common.bs), true);
|
|
||||||
}
|
|
||||||
qemu_aio_release(aio_cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const AIOCBInfo archipelago_aiocb_info = {
|
static const AIOCBInfo archipelago_aiocb_info = {
|
||||||
.aiocb_size = sizeof(ArchipelagoAIOCB),
|
.aiocb_size = sizeof(ArchipelagoAIOCB),
|
||||||
.cancel = qemu_archipelago_aio_cancel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int archipelago_submit_request(BDRVArchipelagoState *s,
|
static int archipelago_submit_request(BDRVArchipelagoState *s,
|
||||||
@ -889,7 +875,6 @@ static BlockDriverAIOCB *qemu_archipelago_aio_rw(BlockDriverState *bs,
|
|||||||
|
|
||||||
aio_cb->ret = 0;
|
aio_cb->ret = 0;
|
||||||
aio_cb->s = s;
|
aio_cb->s = s;
|
||||||
aio_cb->cancelled = false;
|
|
||||||
aio_cb->status = -EINPROGRESS;
|
aio_cb->status = -EINPROGRESS;
|
||||||
|
|
||||||
off = sector_num * BDRV_SECTOR_SIZE;
|
off = sector_num * BDRV_SECTOR_SIZE;
|
||||||
@ -905,7 +890,7 @@ static BlockDriverAIOCB *qemu_archipelago_aio_rw(BlockDriverState *bs,
|
|||||||
|
|
||||||
err_exit:
|
err_exit:
|
||||||
error_report("qemu_archipelago_aio_rw(): I/O Error\n");
|
error_report("qemu_archipelago_aio_rw(): I/O Error\n");
|
||||||
qemu_aio_release(aio_cb);
|
qemu_aio_unref(aio_cb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,7 +993,7 @@ static int qemu_archipelago_truncate(BlockDriverState *bs, int64_t offset)
|
|||||||
req = xseg_get_request(s->xseg, s->srcport, s->mportno, X_ALLOC);
|
req = xseg_get_request(s->xseg, s->srcport, s->mportno, X_ALLOC);
|
||||||
if (!req) {
|
if (!req) {
|
||||||
archipelagolog("Cannot get XSEG request\n");
|
archipelagolog("Cannot get XSEG request\n");
|
||||||
return err_exit2;
|
goto err_exit2;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = xseg_prep_request(s->xseg, req, targetlen, 0);
|
ret = xseg_prep_request(s->xseg, req, targetlen, 0);
|
||||||
|
@ -52,11 +52,8 @@ typedef struct BlkdebugSuspendedReq {
|
|||||||
QLIST_ENTRY(BlkdebugSuspendedReq) next;
|
QLIST_ENTRY(BlkdebugSuspendedReq) next;
|
||||||
} BlkdebugSuspendedReq;
|
} BlkdebugSuspendedReq;
|
||||||
|
|
||||||
static void blkdebug_aio_cancel(BlockDriverAIOCB *blockacb);
|
|
||||||
|
|
||||||
static const AIOCBInfo blkdebug_aiocb_info = {
|
static const AIOCBInfo blkdebug_aiocb_info = {
|
||||||
.aiocb_size = sizeof(BlkdebugAIOCB),
|
.aiocb_size = sizeof(BlkdebugAIOCB),
|
||||||
.cancel = blkdebug_aio_cancel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -447,17 +444,7 @@ static void error_callback_bh(void *opaque)
|
|||||||
struct BlkdebugAIOCB *acb = opaque;
|
struct BlkdebugAIOCB *acb = opaque;
|
||||||
qemu_bh_delete(acb->bh);
|
qemu_bh_delete(acb->bh);
|
||||||
acb->common.cb(acb->common.opaque, acb->ret);
|
acb->common.cb(acb->common.opaque, acb->ret);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
}
|
|
||||||
|
|
||||||
static void blkdebug_aio_cancel(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
BlkdebugAIOCB *acb = container_of(blockacb, BlkdebugAIOCB, common);
|
|
||||||
if (acb->bh) {
|
|
||||||
qemu_bh_delete(acb->bh);
|
|
||||||
acb->bh = NULL;
|
|
||||||
}
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockDriverAIOCB *inject_error(BlockDriverState *bs,
|
static BlockDriverAIOCB *inject_error(BlockDriverState *bs,
|
||||||
|
@ -29,7 +29,6 @@ struct BlkverifyAIOCB {
|
|||||||
|
|
||||||
int ret; /* first completed request's result */
|
int ret; /* first completed request's result */
|
||||||
unsigned int done; /* completion counter */
|
unsigned int done; /* completion counter */
|
||||||
bool *finished; /* completion signal for cancel */
|
|
||||||
|
|
||||||
QEMUIOVector *qiov; /* user I/O vector */
|
QEMUIOVector *qiov; /* user I/O vector */
|
||||||
QEMUIOVector raw_qiov; /* cloned I/O vector for raw file */
|
QEMUIOVector raw_qiov; /* cloned I/O vector for raw file */
|
||||||
@ -38,22 +37,8 @@ struct BlkverifyAIOCB {
|
|||||||
void (*verify)(BlkverifyAIOCB *acb);
|
void (*verify)(BlkverifyAIOCB *acb);
|
||||||
};
|
};
|
||||||
|
|
||||||
static void blkverify_aio_cancel(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
BlkverifyAIOCB *acb = (BlkverifyAIOCB *)blockacb;
|
|
||||||
AioContext *aio_context = bdrv_get_aio_context(blockacb->bs);
|
|
||||||
bool finished = false;
|
|
||||||
|
|
||||||
/* Wait until request completes, invokes its callback, and frees itself */
|
|
||||||
acb->finished = &finished;
|
|
||||||
while (!finished) {
|
|
||||||
aio_poll(aio_context, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const AIOCBInfo blkverify_aiocb_info = {
|
static const AIOCBInfo blkverify_aiocb_info = {
|
||||||
.aiocb_size = sizeof(BlkverifyAIOCB),
|
.aiocb_size = sizeof(BlkverifyAIOCB),
|
||||||
.cancel = blkverify_aio_cancel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void GCC_FMT_ATTR(2, 3) blkverify_err(BlkverifyAIOCB *acb,
|
static void GCC_FMT_ATTR(2, 3) blkverify_err(BlkverifyAIOCB *acb,
|
||||||
@ -194,7 +179,6 @@ static BlkverifyAIOCB *blkverify_aio_get(BlockDriverState *bs, bool is_write,
|
|||||||
acb->qiov = qiov;
|
acb->qiov = qiov;
|
||||||
acb->buf = NULL;
|
acb->buf = NULL;
|
||||||
acb->verify = NULL;
|
acb->verify = NULL;
|
||||||
acb->finished = NULL;
|
|
||||||
return acb;
|
return acb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,10 +192,7 @@ static void blkverify_aio_bh(void *opaque)
|
|||||||
qemu_vfree(acb->buf);
|
qemu_vfree(acb->buf);
|
||||||
}
|
}
|
||||||
acb->common.cb(acb->common.opaque, acb->ret);
|
acb->common.cb(acb->common.opaque, acb->ret);
|
||||||
if (acb->finished) {
|
qemu_aio_unref(acb);
|
||||||
*acb->finished = true;
|
|
||||||
}
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void blkverify_aio_cb(void *opaque, int ret)
|
static void blkverify_aio_cb(void *opaque, int ret)
|
||||||
|
433
block/cow.c
433
block/cow.c
@ -1,433 +0,0 @@
|
|||||||
/*
|
|
||||||
* Block driver for the COW format
|
|
||||||
*
|
|
||||||
* Copyright (c) 2004 Fabrice Bellard
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "qemu/module.h"
|
|
||||||
|
|
||||||
/**************************************************************/
|
|
||||||
/* COW block driver using file system holes */
|
|
||||||
|
|
||||||
/* user mode linux compatible COW file */
|
|
||||||
#define COW_MAGIC 0x4f4f4f4d /* MOOO */
|
|
||||||
#define COW_VERSION 2
|
|
||||||
|
|
||||||
struct cow_header_v2 {
|
|
||||||
uint32_t magic;
|
|
||||||
uint32_t version;
|
|
||||||
char backing_file[1024];
|
|
||||||
int32_t mtime;
|
|
||||||
uint64_t size;
|
|
||||||
uint32_t sectorsize;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct BDRVCowState {
|
|
||||||
CoMutex lock;
|
|
||||||
int64_t cow_sectors_offset;
|
|
||||||
} BDRVCowState;
|
|
||||||
|
|
||||||
static int cow_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|
||||||
{
|
|
||||||
const struct cow_header_v2 *cow_header = (const void *)buf;
|
|
||||||
|
|
||||||
if (buf_size >= sizeof(struct cow_header_v2) &&
|
|
||||||
be32_to_cpu(cow_header->magic) == COW_MAGIC &&
|
|
||||||
be32_to_cpu(cow_header->version) == COW_VERSION)
|
|
||||||
return 100;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cow_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BDRVCowState *s = bs->opaque;
|
|
||||||
struct cow_header_v2 cow_header;
|
|
||||||
int bitmap_size;
|
|
||||||
int64_t size;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* see if it is a cow image */
|
|
||||||
ret = bdrv_pread(bs->file, 0, &cow_header, sizeof(cow_header));
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (be32_to_cpu(cow_header.magic) != COW_MAGIC) {
|
|
||||||
error_setg(errp, "Image not in COW format");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (be32_to_cpu(cow_header.version) != COW_VERSION) {
|
|
||||||
char version[64];
|
|
||||||
snprintf(version, sizeof(version),
|
|
||||||
"COW version %" PRIu32, cow_header.version);
|
|
||||||
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
|
||||||
bs->device_name, "cow", version);
|
|
||||||
ret = -ENOTSUP;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* cow image found */
|
|
||||||
size = be64_to_cpu(cow_header.size);
|
|
||||||
bs->total_sectors = size / 512;
|
|
||||||
|
|
||||||
pstrcpy(bs->backing_file, sizeof(bs->backing_file),
|
|
||||||
cow_header.backing_file);
|
|
||||||
|
|
||||||
bitmap_size = ((bs->total_sectors + 7) >> 3) + sizeof(cow_header);
|
|
||||||
s->cow_sectors_offset = (bitmap_size + 511) & ~511;
|
|
||||||
qemu_co_mutex_init(&s->lock);
|
|
||||||
return 0;
|
|
||||||
fail:
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void cow_set_bits(uint8_t *bitmap, int start, int64_t nb_sectors)
|
|
||||||
{
|
|
||||||
int64_t bitnum = start, last = start + nb_sectors;
|
|
||||||
while (bitnum < last) {
|
|
||||||
if ((bitnum & 7) == 0 && bitnum + 8 <= last) {
|
|
||||||
bitmap[bitnum / 8] = 0xFF;
|
|
||||||
bitnum += 8;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bitmap[bitnum/8] |= (1 << (bitnum % 8));
|
|
||||||
bitnum++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define BITS_PER_BITMAP_SECTOR (512 * 8)
|
|
||||||
|
|
||||||
/* Cannot use bitmap.c on big-endian machines. */
|
|
||||||
static int cow_test_bit(int64_t bitnum, const uint8_t *bitmap)
|
|
||||||
{
|
|
||||||
return (bitmap[bitnum / 8] & (1 << (bitnum & 7))) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cow_find_streak(const uint8_t *bitmap, int value, int start, int nb_sectors)
|
|
||||||
{
|
|
||||||
int streak_value = value ? 0xFF : 0;
|
|
||||||
int last = MIN(start + nb_sectors, BITS_PER_BITMAP_SECTOR);
|
|
||||||
int bitnum = start;
|
|
||||||
while (bitnum < last) {
|
|
||||||
if ((bitnum & 7) == 0 && bitmap[bitnum / 8] == streak_value) {
|
|
||||||
bitnum += 8;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cow_test_bit(bitnum, bitmap) == value) {
|
|
||||||
bitnum++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return MIN(bitnum, last) - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return true if first block has been changed (ie. current version is
|
|
||||||
* in COW file). Set the number of continuous blocks for which that
|
|
||||||
* is true. */
|
|
||||||
static int coroutine_fn cow_co_is_allocated(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors, int *num_same)
|
|
||||||
{
|
|
||||||
int64_t bitnum = sector_num + sizeof(struct cow_header_v2) * 8;
|
|
||||||
uint64_t offset = (bitnum / 8) & -BDRV_SECTOR_SIZE;
|
|
||||||
bool first = true;
|
|
||||||
int changed = 0, same = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
int ret;
|
|
||||||
uint8_t bitmap[BDRV_SECTOR_SIZE];
|
|
||||||
|
|
||||||
bitnum &= BITS_PER_BITMAP_SECTOR - 1;
|
|
||||||
int sector_bits = MIN(nb_sectors, BITS_PER_BITMAP_SECTOR - bitnum);
|
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, offset, &bitmap, sizeof(bitmap));
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
changed = cow_test_bit(bitnum, bitmap);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
same += cow_find_streak(bitmap, changed, bitnum, nb_sectors);
|
|
||||||
|
|
||||||
bitnum += sector_bits;
|
|
||||||
nb_sectors -= sector_bits;
|
|
||||||
offset += BDRV_SECTOR_SIZE;
|
|
||||||
} while (nb_sectors);
|
|
||||||
|
|
||||||
*num_same = same;
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t coroutine_fn cow_co_get_block_status(BlockDriverState *bs,
|
|
||||||
int64_t sector_num, int nb_sectors, int *num_same)
|
|
||||||
{
|
|
||||||
BDRVCowState *s = bs->opaque;
|
|
||||||
int ret = cow_co_is_allocated(bs, sector_num, nb_sectors, num_same);
|
|
||||||
int64_t offset = s->cow_sectors_offset + (sector_num << BDRV_SECTOR_BITS);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return (ret ? BDRV_BLOCK_DATA : 0) | offset | BDRV_BLOCK_OFFSET_VALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cow_update_bitmap(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
int nb_sectors)
|
|
||||||
{
|
|
||||||
int64_t bitnum = sector_num + sizeof(struct cow_header_v2) * 8;
|
|
||||||
uint64_t offset = (bitnum / 8) & -BDRV_SECTOR_SIZE;
|
|
||||||
bool first = true;
|
|
||||||
int sector_bits;
|
|
||||||
|
|
||||||
for ( ; nb_sectors;
|
|
||||||
bitnum += sector_bits,
|
|
||||||
nb_sectors -= sector_bits,
|
|
||||||
offset += BDRV_SECTOR_SIZE) {
|
|
||||||
int ret, set;
|
|
||||||
uint8_t bitmap[BDRV_SECTOR_SIZE];
|
|
||||||
|
|
||||||
bitnum &= BITS_PER_BITMAP_SECTOR - 1;
|
|
||||||
sector_bits = MIN(nb_sectors, BITS_PER_BITMAP_SECTOR - bitnum);
|
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, offset, &bitmap, sizeof(bitmap));
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skip over any already set bits */
|
|
||||||
set = cow_find_streak(bitmap, 1, bitnum, sector_bits);
|
|
||||||
bitnum += set;
|
|
||||||
sector_bits -= set;
|
|
||||||
nb_sectors -= set;
|
|
||||||
if (!sector_bits) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
ret = bdrv_flush(bs->file);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cow_set_bits(bitmap, bitnum, sector_bits);
|
|
||||||
|
|
||||||
ret = bdrv_pwrite(bs->file, offset, &bitmap, sizeof(bitmap));
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn cow_read(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
uint8_t *buf, int nb_sectors)
|
|
||||||
{
|
|
||||||
BDRVCowState *s = bs->opaque;
|
|
||||||
int ret, n;
|
|
||||||
|
|
||||||
while (nb_sectors > 0) {
|
|
||||||
ret = cow_co_is_allocated(bs, sector_num, nb_sectors, &n);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
if (ret) {
|
|
||||||
ret = bdrv_pread(bs->file,
|
|
||||||
s->cow_sectors_offset + sector_num * 512,
|
|
||||||
buf, n * 512);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (bs->backing_hd) {
|
|
||||||
/* read from the base image */
|
|
||||||
ret = bdrv_read(bs->backing_hd, sector_num, buf, n);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
memset(buf, 0, n * 512);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nb_sectors -= n;
|
|
||||||
sector_num += n;
|
|
||||||
buf += n * 512;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static coroutine_fn int cow_co_read(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
uint8_t *buf, int nb_sectors)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
BDRVCowState *s = bs->opaque;
|
|
||||||
qemu_co_mutex_lock(&s->lock);
|
|
||||||
ret = cow_read(bs, sector_num, buf, nb_sectors);
|
|
||||||
qemu_co_mutex_unlock(&s->lock);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cow_write(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
const uint8_t *buf, int nb_sectors)
|
|
||||||
{
|
|
||||||
BDRVCowState *s = bs->opaque;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = bdrv_pwrite(bs->file, s->cow_sectors_offset + sector_num * 512,
|
|
||||||
buf, nb_sectors * 512);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cow_update_bitmap(bs, sector_num, nb_sectors);
|
|
||||||
}
|
|
||||||
|
|
||||||
static coroutine_fn int cow_co_write(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
const uint8_t *buf, int nb_sectors)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
BDRVCowState *s = bs->opaque;
|
|
||||||
qemu_co_mutex_lock(&s->lock);
|
|
||||||
ret = cow_write(bs, sector_num, buf, nb_sectors);
|
|
||||||
qemu_co_mutex_unlock(&s->lock);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cow_close(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cow_create(const char *filename, QemuOpts *opts, Error **errp)
|
|
||||||
{
|
|
||||||
struct cow_header_v2 cow_header;
|
|
||||||
struct stat st;
|
|
||||||
int64_t image_sectors = 0;
|
|
||||||
char *image_filename = NULL;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
|
||||||
BlockDriverState *cow_bs = NULL;
|
|
||||||
|
|
||||||
/* Read out options */
|
|
||||||
image_sectors = DIV_ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
|
||||||
BDRV_SECTOR_SIZE);
|
|
||||||
image_filename = qemu_opt_get_del(opts, BLOCK_OPT_BACKING_FILE);
|
|
||||||
|
|
||||||
ret = bdrv_create_file(filename, opts, &local_err);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bdrv_open(&cow_bs, filename, NULL, NULL,
|
|
||||||
BDRV_O_RDWR | BDRV_O_PROTOCOL, NULL, &local_err);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&cow_header, 0, sizeof(cow_header));
|
|
||||||
cow_header.magic = cpu_to_be32(COW_MAGIC);
|
|
||||||
cow_header.version = cpu_to_be32(COW_VERSION);
|
|
||||||
if (image_filename) {
|
|
||||||
/* Note: if no file, we put a dummy mtime */
|
|
||||||
cow_header.mtime = cpu_to_be32(0);
|
|
||||||
|
|
||||||
if (stat(image_filename, &st) != 0) {
|
|
||||||
goto mtime_fail;
|
|
||||||
}
|
|
||||||
cow_header.mtime = cpu_to_be32(st.st_mtime);
|
|
||||||
mtime_fail:
|
|
||||||
pstrcpy(cow_header.backing_file, sizeof(cow_header.backing_file),
|
|
||||||
image_filename);
|
|
||||||
}
|
|
||||||
cow_header.sectorsize = cpu_to_be32(512);
|
|
||||||
cow_header.size = cpu_to_be64(image_sectors * 512);
|
|
||||||
ret = bdrv_pwrite(cow_bs, 0, &cow_header, sizeof(cow_header));
|
|
||||||
if (ret < 0) {
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* resize to include at least all the bitmap */
|
|
||||||
ret = bdrv_truncate(cow_bs,
|
|
||||||
sizeof(cow_header) + ((image_sectors + 7) >> 3));
|
|
||||||
if (ret < 0) {
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
exit:
|
|
||||||
g_free(image_filename);
|
|
||||||
if (cow_bs) {
|
|
||||||
bdrv_unref(cow_bs);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QemuOptsList cow_create_opts = {
|
|
||||||
.name = "cow-create-opts",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(cow_create_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = BLOCK_OPT_SIZE,
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Virtual disk size"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = BLOCK_OPT_BACKING_FILE,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "File name of a base image"
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static BlockDriver bdrv_cow = {
|
|
||||||
.format_name = "cow",
|
|
||||||
.instance_size = sizeof(BDRVCowState),
|
|
||||||
|
|
||||||
.bdrv_probe = cow_probe,
|
|
||||||
.bdrv_open = cow_open,
|
|
||||||
.bdrv_close = cow_close,
|
|
||||||
.bdrv_create = cow_create,
|
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
|
||||||
.supports_backing = true,
|
|
||||||
|
|
||||||
.bdrv_read = cow_co_read,
|
|
||||||
.bdrv_write = cow_co_write,
|
|
||||||
.bdrv_co_get_block_status = cow_co_get_block_status,
|
|
||||||
|
|
||||||
.create_opts = &cow_create_opts,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void bdrv_cow_init(void)
|
|
||||||
{
|
|
||||||
bdrv_register(&bdrv_cow);
|
|
||||||
}
|
|
||||||
|
|
||||||
block_init(bdrv_cow_init);
|
|
16
block/curl.c
16
block/curl.c
@ -212,7 +212,7 @@ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
|||||||
qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start,
|
qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start,
|
||||||
acb->end - acb->start);
|
acb->end - acb->start);
|
||||||
acb->common.cb(acb->common.opaque, 0);
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
s->acb[i] = NULL;
|
s->acb[i] = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,7 +304,7 @@ static void curl_multi_check_completion(BDRVCURLState *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
acb->common.cb(acb->common.opaque, -EIO);
|
acb->common.cb(acb->common.opaque, -EIO);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
state->acb[i] = NULL;
|
state->acb[i] = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -613,14 +613,8 @@ out_noclean:
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_aio_cancel(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
// Do we have to implement canceling? Seems to work without...
|
|
||||||
}
|
|
||||||
|
|
||||||
static const AIOCBInfo curl_aiocb_info = {
|
static const AIOCBInfo curl_aiocb_info = {
|
||||||
.aiocb_size = sizeof(CURLAIOCB),
|
.aiocb_size = sizeof(CURLAIOCB),
|
||||||
.cancel = curl_aio_cancel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -642,7 +636,7 @@ static void curl_readv_bh_cb(void *p)
|
|||||||
// we can just call the callback and be done.
|
// we can just call the callback and be done.
|
||||||
switch (curl_find_buf(s, start, acb->nb_sectors * SECTOR_SIZE, acb)) {
|
switch (curl_find_buf(s, start, acb->nb_sectors * SECTOR_SIZE, acb)) {
|
||||||
case FIND_RET_OK:
|
case FIND_RET_OK:
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
// fall through
|
// fall through
|
||||||
case FIND_RET_WAIT:
|
case FIND_RET_WAIT:
|
||||||
return;
|
return;
|
||||||
@ -654,7 +648,7 @@ static void curl_readv_bh_cb(void *p)
|
|||||||
state = curl_init_state(acb->common.bs, s);
|
state = curl_init_state(acb->common.bs, s);
|
||||||
if (!state) {
|
if (!state) {
|
||||||
acb->common.cb(acb->common.opaque, -EIO);
|
acb->common.cb(acb->common.opaque, -EIO);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -670,7 +664,7 @@ static void curl_readv_bh_cb(void *p)
|
|||||||
if (state->buf_len && state->orig_buf == NULL) {
|
if (state->buf_len && state->orig_buf == NULL) {
|
||||||
curl_clean_state(state);
|
curl_clean_state(state);
|
||||||
acb->common.cb(acb->common.opaque, -ENOMEM);
|
acb->common.cb(acb->common.opaque, -ENOMEM);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state->acb[0] = acb;
|
state->acb[0] = acb;
|
||||||
|
@ -88,7 +88,6 @@ typedef struct IscsiAIOCB {
|
|||||||
struct scsi_task *task;
|
struct scsi_task *task;
|
||||||
uint8_t *buf;
|
uint8_t *buf;
|
||||||
int status;
|
int status;
|
||||||
int canceled;
|
|
||||||
int64_t sector_num;
|
int64_t sector_num;
|
||||||
int nb_sectors;
|
int nb_sectors;
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
@ -120,16 +119,14 @@ iscsi_bh_cb(void *p)
|
|||||||
g_free(acb->buf);
|
g_free(acb->buf);
|
||||||
acb->buf = NULL;
|
acb->buf = NULL;
|
||||||
|
|
||||||
if (acb->canceled == 0) {
|
acb->common.cb(acb->common.opaque, acb->status);
|
||||||
acb->common.cb(acb->common.opaque, acb->status);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (acb->task != NULL) {
|
if (acb->task != NULL) {
|
||||||
scsi_free_scsi_task(acb->task);
|
scsi_free_scsi_task(acb->task);
|
||||||
acb->task = NULL;
|
acb->task = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -240,20 +237,15 @@ iscsi_aio_cancel(BlockDriverAIOCB *blockacb)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
acb->canceled = 1;
|
|
||||||
|
|
||||||
/* send a task mgmt call to the target to cancel the task on the target */
|
/* send a task mgmt call to the target to cancel the task on the target */
|
||||||
iscsi_task_mgmt_abort_task_async(iscsilun->iscsi, acb->task,
|
iscsi_task_mgmt_abort_task_async(iscsilun->iscsi, acb->task,
|
||||||
iscsi_abort_task_cb, acb);
|
iscsi_abort_task_cb, acb);
|
||||||
|
|
||||||
while (acb->status == -EINPROGRESS) {
|
|
||||||
aio_poll(iscsilun->aio_context, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const AIOCBInfo iscsi_aiocb_info = {
|
static const AIOCBInfo iscsi_aiocb_info = {
|
||||||
.aiocb_size = sizeof(IscsiAIOCB),
|
.aiocb_size = sizeof(IscsiAIOCB),
|
||||||
.cancel = iscsi_aio_cancel,
|
.cancel_async = iscsi_aio_cancel,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -638,10 +630,6 @@ iscsi_aio_ioctl_cb(struct iscsi_context *iscsi, int status,
|
|||||||
g_free(acb->buf);
|
g_free(acb->buf);
|
||||||
acb->buf = NULL;
|
acb->buf = NULL;
|
||||||
|
|
||||||
if (acb->canceled != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
acb->status = 0;
|
acb->status = 0;
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
error_report("Failed to ioctl(SG_IO) to iSCSI lun. %s",
|
error_report("Failed to ioctl(SG_IO) to iSCSI lun. %s",
|
||||||
@ -683,7 +671,6 @@ static BlockDriverAIOCB *iscsi_aio_ioctl(BlockDriverState *bs,
|
|||||||
acb = qemu_aio_get(&iscsi_aiocb_info, bs, cb, opaque);
|
acb = qemu_aio_get(&iscsi_aiocb_info, bs, cb, opaque);
|
||||||
|
|
||||||
acb->iscsilun = iscsilun;
|
acb->iscsilun = iscsilun;
|
||||||
acb->canceled = 0;
|
|
||||||
acb->bh = NULL;
|
acb->bh = NULL;
|
||||||
acb->status = -EINPROGRESS;
|
acb->status = -EINPROGRESS;
|
||||||
acb->buf = NULL;
|
acb->buf = NULL;
|
||||||
@ -693,7 +680,7 @@ static BlockDriverAIOCB *iscsi_aio_ioctl(BlockDriverState *bs,
|
|||||||
if (acb->task == NULL) {
|
if (acb->task == NULL) {
|
||||||
error_report("iSCSI: Failed to allocate task for scsi command. %s",
|
error_report("iSCSI: Failed to allocate task for scsi command. %s",
|
||||||
iscsi_get_error(iscsi));
|
iscsi_get_error(iscsi));
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
memset(acb->task, 0, sizeof(struct scsi_task));
|
memset(acb->task, 0, sizeof(struct scsi_task));
|
||||||
@ -731,7 +718,7 @@ static BlockDriverAIOCB *iscsi_aio_ioctl(BlockDriverState *bs,
|
|||||||
(data.size > 0) ? &data : NULL,
|
(data.size > 0) ? &data : NULL,
|
||||||
acb) != 0) {
|
acb) != 0) {
|
||||||
scsi_free_scsi_task(acb->task);
|
scsi_free_scsi_task(acb->task);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,11 +85,10 @@ static void qemu_laio_process_completion(struct qemu_laio_state *s,
|
|||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
laiocb->common.cb(laiocb->common.opaque, ret);
|
|
||||||
}
|
}
|
||||||
|
laiocb->common.cb(laiocb->common.opaque, ret);
|
||||||
|
|
||||||
qemu_aio_release(laiocb);
|
qemu_aio_unref(laiocb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The completion BH fetches completed I/O requests and invokes their
|
/* The completion BH fetches completed I/O requests and invokes their
|
||||||
@ -153,35 +152,22 @@ static void laio_cancel(BlockDriverAIOCB *blockacb)
|
|||||||
struct io_event event;
|
struct io_event event;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (laiocb->ret != -EINPROGRESS)
|
if (laiocb->ret != -EINPROGRESS) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
/*
|
|
||||||
* Note that as of Linux 2.6.31 neither the block device code nor any
|
|
||||||
* filesystem implements cancellation of AIO request.
|
|
||||||
* Thus the polling loop below is the normal code path.
|
|
||||||
*/
|
|
||||||
ret = io_cancel(laiocb->ctx->ctx, &laiocb->iocb, &event);
|
ret = io_cancel(laiocb->ctx->ctx, &laiocb->iocb, &event);
|
||||||
if (ret == 0) {
|
laiocb->ret = -ECANCELED;
|
||||||
laiocb->ret = -ECANCELED;
|
if (ret != 0) {
|
||||||
|
/* iocb is not cancelled, cb will be called by the event loop later */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
laiocb->common.cb(laiocb->common.opaque, laiocb->ret);
|
||||||
* We have to wait for the iocb to finish.
|
|
||||||
*
|
|
||||||
* The only way to get the iocb status update is by polling the io context.
|
|
||||||
* We might be able to do this slightly more optimal by removing the
|
|
||||||
* O_NONBLOCK flag.
|
|
||||||
*/
|
|
||||||
while (laiocb->ret == -EINPROGRESS) {
|
|
||||||
qemu_laio_completion_cb(&laiocb->ctx->e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const AIOCBInfo laio_aiocb_info = {
|
static const AIOCBInfo laio_aiocb_info = {
|
||||||
.aiocb_size = sizeof(struct qemu_laiocb),
|
.aiocb_size = sizeof(struct qemu_laiocb),
|
||||||
.cancel = laio_cancel,
|
.cancel_async = laio_cancel,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void ioq_init(LaioQueue *io_q)
|
static void ioq_init(LaioQueue *io_q)
|
||||||
@ -300,7 +286,7 @@ BlockDriverAIOCB *laio_submit(BlockDriverState *bs, void *aio_ctx, int fd,
|
|||||||
return &laiocb->common;
|
return &laiocb->common;
|
||||||
|
|
||||||
out_free_aiocb:
|
out_free_aiocb:
|
||||||
qemu_aio_release(laiocb);
|
qemu_aio_unref(laiocb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
168
block/null.c
Normal file
168
block/null.c
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Null block driver
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Fam Zheng <famz@redhat.com>
|
||||||
|
*
|
||||||
|
* Copyright (C) 2014 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "block/block_int.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int64_t length;
|
||||||
|
} BDRVNullState;
|
||||||
|
|
||||||
|
static QemuOptsList runtime_opts = {
|
||||||
|
.name = "null",
|
||||||
|
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
||||||
|
.desc = {
|
||||||
|
{
|
||||||
|
.name = "filename",
|
||||||
|
.type = QEMU_OPT_STRING,
|
||||||
|
.help = "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = BLOCK_OPT_SIZE,
|
||||||
|
.type = QEMU_OPT_SIZE,
|
||||||
|
.help = "size of the null block",
|
||||||
|
},
|
||||||
|
{ /* end of list */ }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int null_file_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
QemuOpts *opts;
|
||||||
|
BDRVNullState *s = bs->opaque;
|
||||||
|
|
||||||
|
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||||
|
qemu_opts_absorb_qdict(opts, options, &error_abort);
|
||||||
|
s->length =
|
||||||
|
qemu_opt_get_size(opts, BLOCK_OPT_SIZE, 1 << 30);
|
||||||
|
qemu_opts_del(opts);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void null_close(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t null_getlength(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
BDRVNullState *s = bs->opaque;
|
||||||
|
return s->length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static coroutine_fn int null_co_readv(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, int nb_sectors,
|
||||||
|
QEMUIOVector *qiov)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static coroutine_fn int null_co_writev(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, int nb_sectors,
|
||||||
|
QEMUIOVector *qiov)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static coroutine_fn int null_co_flush(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BlockDriverAIOCB common;
|
||||||
|
QEMUBH *bh;
|
||||||
|
} NullAIOCB;
|
||||||
|
|
||||||
|
static const AIOCBInfo null_aiocb_info = {
|
||||||
|
.aiocb_size = sizeof(NullAIOCB),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void null_bh_cb(void *opaque)
|
||||||
|
{
|
||||||
|
NullAIOCB *acb = opaque;
|
||||||
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
|
qemu_bh_delete(acb->bh);
|
||||||
|
qemu_aio_unref(acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline BlockDriverAIOCB *null_aio_common(BlockDriverState *bs,
|
||||||
|
BlockDriverCompletionFunc *cb,
|
||||||
|
void *opaque)
|
||||||
|
{
|
||||||
|
NullAIOCB *acb;
|
||||||
|
|
||||||
|
acb = qemu_aio_get(&null_aiocb_info, bs, cb, opaque);
|
||||||
|
acb->bh = aio_bh_new(bdrv_get_aio_context(bs), null_bh_cb, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
|
return &acb->common;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriverAIOCB *null_aio_readv(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov,
|
||||||
|
int nb_sectors,
|
||||||
|
BlockDriverCompletionFunc *cb,
|
||||||
|
void *opaque)
|
||||||
|
{
|
||||||
|
return null_aio_common(bs, cb, opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriverAIOCB *null_aio_writev(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov,
|
||||||
|
int nb_sectors,
|
||||||
|
BlockDriverCompletionFunc *cb,
|
||||||
|
void *opaque)
|
||||||
|
{
|
||||||
|
return null_aio_common(bs, cb, opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriverAIOCB *null_aio_flush(BlockDriverState *bs,
|
||||||
|
BlockDriverCompletionFunc *cb,
|
||||||
|
void *opaque)
|
||||||
|
{
|
||||||
|
return null_aio_common(bs, cb, opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriver bdrv_null_co = {
|
||||||
|
.format_name = "null-co",
|
||||||
|
.protocol_name = "null-co",
|
||||||
|
.instance_size = sizeof(BDRVNullState),
|
||||||
|
|
||||||
|
.bdrv_file_open = null_file_open,
|
||||||
|
.bdrv_close = null_close,
|
||||||
|
.bdrv_getlength = null_getlength,
|
||||||
|
|
||||||
|
.bdrv_co_readv = null_co_readv,
|
||||||
|
.bdrv_co_writev = null_co_writev,
|
||||||
|
.bdrv_co_flush_to_disk = null_co_flush,
|
||||||
|
};
|
||||||
|
|
||||||
|
static BlockDriver bdrv_null_aio = {
|
||||||
|
.format_name = "null-aio",
|
||||||
|
.protocol_name = "null-aio",
|
||||||
|
.instance_size = sizeof(BDRVNullState),
|
||||||
|
|
||||||
|
.bdrv_file_open = null_file_open,
|
||||||
|
.bdrv_close = null_close,
|
||||||
|
.bdrv_getlength = null_getlength,
|
||||||
|
|
||||||
|
.bdrv_aio_readv = null_aio_readv,
|
||||||
|
.bdrv_aio_writev = null_aio_writev,
|
||||||
|
.bdrv_aio_flush = null_aio_flush,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void bdrv_null_init(void)
|
||||||
|
{
|
||||||
|
bdrv_register(&bdrv_null_co);
|
||||||
|
bdrv_register(&bdrv_null_aio);
|
||||||
|
}
|
||||||
|
|
||||||
|
block_init(bdrv_null_init);
|
@ -486,6 +486,13 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (offset_into_cluster(s, l2_offset)) {
|
||||||
|
qcow2_signal_corruption(bs, true, -1, -1, "L2 table offset %#" PRIx64
|
||||||
|
" unaligned (L1 index: %#" PRIx64 ")",
|
||||||
|
l2_offset, l1_index);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
/* load the l2 table in memory */
|
/* load the l2 table in memory */
|
||||||
|
|
||||||
ret = l2_load(bs, l2_offset, &l2_table);
|
ret = l2_load(bs, l2_offset, &l2_table);
|
||||||
@ -508,8 +515,11 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
|||||||
break;
|
break;
|
||||||
case QCOW2_CLUSTER_ZERO:
|
case QCOW2_CLUSTER_ZERO:
|
||||||
if (s->qcow_version < 3) {
|
if (s->qcow_version < 3) {
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
qcow2_signal_corruption(bs, true, -1, -1, "Zero cluster entry found"
|
||||||
return -EIO;
|
" in pre-v3 image (L2 offset: %#" PRIx64
|
||||||
|
", L2 index: %#x)", l2_offset, l2_index);
|
||||||
|
ret = -EIO;
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
c = count_contiguous_clusters(nb_clusters, s->cluster_size,
|
c = count_contiguous_clusters(nb_clusters, s->cluster_size,
|
||||||
&l2_table[l2_index], QCOW_OFLAG_ZERO);
|
&l2_table[l2_index], QCOW_OFLAG_ZERO);
|
||||||
@ -525,6 +535,14 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
|||||||
c = count_contiguous_clusters(nb_clusters, s->cluster_size,
|
c = count_contiguous_clusters(nb_clusters, s->cluster_size,
|
||||||
&l2_table[l2_index], QCOW_OFLAG_ZERO);
|
&l2_table[l2_index], QCOW_OFLAG_ZERO);
|
||||||
*cluster_offset &= L2E_OFFSET_MASK;
|
*cluster_offset &= L2E_OFFSET_MASK;
|
||||||
|
if (offset_into_cluster(s, *cluster_offset)) {
|
||||||
|
qcow2_signal_corruption(bs, true, -1, -1, "Data cluster offset %#"
|
||||||
|
PRIx64 " unaligned (L2 offset: %#" PRIx64
|
||||||
|
", L2 index: %#x)", *cluster_offset,
|
||||||
|
l2_offset, l2_index);
|
||||||
|
ret = -EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
abort();
|
abort();
|
||||||
@ -541,6 +559,10 @@ out:
|
|||||||
*num = nb_available - index_in_cluster;
|
*num = nb_available - index_in_cluster;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
qcow2_cache_put(bs, s->l2_table_cache, (void **)&l2_table);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -576,6 +598,12 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
|||||||
|
|
||||||
assert(l1_index < s->l1_size);
|
assert(l1_index < s->l1_size);
|
||||||
l2_offset = s->l1_table[l1_index] & L1E_OFFSET_MASK;
|
l2_offset = s->l1_table[l1_index] & L1E_OFFSET_MASK;
|
||||||
|
if (offset_into_cluster(s, l2_offset)) {
|
||||||
|
qcow2_signal_corruption(bs, true, -1, -1, "L2 table offset %#" PRIx64
|
||||||
|
" unaligned (L1 index: %#" PRIx64 ")",
|
||||||
|
l2_offset, l1_index);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
/* seek the l2 table of the given l2 offset */
|
/* seek the l2 table of the given l2 offset */
|
||||||
|
|
||||||
@ -948,6 +976,15 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_offset,
|
|||||||
bool offset_matches =
|
bool offset_matches =
|
||||||
(cluster_offset & L2E_OFFSET_MASK) == *host_offset;
|
(cluster_offset & L2E_OFFSET_MASK) == *host_offset;
|
||||||
|
|
||||||
|
if (offset_into_cluster(s, cluster_offset & L2E_OFFSET_MASK)) {
|
||||||
|
qcow2_signal_corruption(bs, true, -1, -1, "Data cluster offset "
|
||||||
|
"%#llx unaligned (guest offset: %#" PRIx64
|
||||||
|
")", cluster_offset & L2E_OFFSET_MASK,
|
||||||
|
guest_offset);
|
||||||
|
ret = -EIO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
if (*host_offset != 0 && !offset_matches) {
|
if (*host_offset != 0 && !offset_matches) {
|
||||||
*bytes = 0;
|
*bytes = 0;
|
||||||
ret = 0;
|
ret = 0;
|
||||||
@ -979,7 +1016,7 @@ out:
|
|||||||
|
|
||||||
/* Only return a host offset if we actually made progress. Otherwise we
|
/* Only return a host offset if we actually made progress. Otherwise we
|
||||||
* would make requirements for handle_alloc() that it can't fulfill */
|
* would make requirements for handle_alloc() that it can't fulfill */
|
||||||
if (ret) {
|
if (ret > 0) {
|
||||||
*host_offset = (cluster_offset & L2E_OFFSET_MASK)
|
*host_offset = (cluster_offset & L2E_OFFSET_MASK)
|
||||||
+ offset_into_cluster(s, guest_offset);
|
+ offset_into_cluster(s, guest_offset);
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,6 @@
|
|||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "block/qcow2.h"
|
#include "block/qcow2.h"
|
||||||
#include "qemu/range.h"
|
#include "qemu/range.h"
|
||||||
#include "qapi/qmp/types.h"
|
|
||||||
#include "qapi-event.h"
|
|
||||||
|
|
||||||
static int64_t alloc_clusters_noref(BlockDriverState *bs, uint64_t size);
|
static int64_t alloc_clusters_noref(BlockDriverState *bs, uint64_t size);
|
||||||
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||||
@ -110,6 +108,13 @@ static int get_refcount(BlockDriverState *bs, int64_t cluster_index)
|
|||||||
if (!refcount_block_offset)
|
if (!refcount_block_offset)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if (offset_into_cluster(s, refcount_block_offset)) {
|
||||||
|
qcow2_signal_corruption(bs, true, -1, -1, "Refblock offset %#" PRIx64
|
||||||
|
" unaligned (reftable index: %#" PRIx64 ")",
|
||||||
|
refcount_block_offset, refcount_table_index);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
ret = qcow2_cache_get(bs, s->refcount_block_cache, refcount_block_offset,
|
ret = qcow2_cache_get(bs, s->refcount_block_cache, refcount_block_offset,
|
||||||
(void**) &refcount_block);
|
(void**) &refcount_block);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@ -183,6 +188,14 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
|||||||
|
|
||||||
/* If it's already there, we're done */
|
/* If it's already there, we're done */
|
||||||
if (refcount_block_offset) {
|
if (refcount_block_offset) {
|
||||||
|
if (offset_into_cluster(s, refcount_block_offset)) {
|
||||||
|
qcow2_signal_corruption(bs, true, -1, -1, "Refblock offset %#"
|
||||||
|
PRIx64 " unaligned (reftable index: "
|
||||||
|
"%#x)", refcount_block_offset,
|
||||||
|
refcount_table_index);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
return load_refcount_block(bs, refcount_block_offset,
|
return load_refcount_block(bs, refcount_block_offset,
|
||||||
(void**) refcount_block);
|
(void**) refcount_block);
|
||||||
}
|
}
|
||||||
@ -838,8 +851,14 @@ void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
|
|||||||
case QCOW2_CLUSTER_NORMAL:
|
case QCOW2_CLUSTER_NORMAL:
|
||||||
case QCOW2_CLUSTER_ZERO:
|
case QCOW2_CLUSTER_ZERO:
|
||||||
if (l2_entry & L2E_OFFSET_MASK) {
|
if (l2_entry & L2E_OFFSET_MASK) {
|
||||||
qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
|
if (offset_into_cluster(s, l2_entry & L2E_OFFSET_MASK)) {
|
||||||
nb_clusters << s->cluster_bits, type);
|
qcow2_signal_corruption(bs, false, -1, -1,
|
||||||
|
"Cannot free unaligned cluster %#llx",
|
||||||
|
l2_entry & L2E_OFFSET_MASK);
|
||||||
|
} else {
|
||||||
|
qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
|
||||||
|
nb_clusters << s->cluster_bits, type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QCOW2_CLUSTER_UNALLOCATED:
|
case QCOW2_CLUSTER_UNALLOCATED:
|
||||||
@ -903,6 +922,14 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
|||||||
old_l2_offset = l2_offset;
|
old_l2_offset = l2_offset;
|
||||||
l2_offset &= L1E_OFFSET_MASK;
|
l2_offset &= L1E_OFFSET_MASK;
|
||||||
|
|
||||||
|
if (offset_into_cluster(s, l2_offset)) {
|
||||||
|
qcow2_signal_corruption(bs, true, -1, -1, "L2 table offset %#"
|
||||||
|
PRIx64 " unaligned (L1 index: %#x)",
|
||||||
|
l2_offset, i);
|
||||||
|
ret = -EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
ret = qcow2_cache_get(bs, s->l2_table_cache, l2_offset,
|
ret = qcow2_cache_get(bs, s->l2_table_cache, l2_offset,
|
||||||
(void**) &l2_table);
|
(void**) &l2_table);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@ -935,6 +962,17 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
|||||||
|
|
||||||
case QCOW2_CLUSTER_NORMAL:
|
case QCOW2_CLUSTER_NORMAL:
|
||||||
case QCOW2_CLUSTER_ZERO:
|
case QCOW2_CLUSTER_ZERO:
|
||||||
|
if (offset_into_cluster(s, offset & L2E_OFFSET_MASK)) {
|
||||||
|
qcow2_signal_corruption(bs, true, -1, -1, "Data "
|
||||||
|
"cluster offset %#llx "
|
||||||
|
"unaligned (L2 offset: %#"
|
||||||
|
PRIx64 ", L2 index: %#x)",
|
||||||
|
offset & L2E_OFFSET_MASK,
|
||||||
|
l2_offset, j);
|
||||||
|
ret = -EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
cluster_index = (offset & L2E_OFFSET_MASK) >> s->cluster_bits;
|
cluster_index = (offset & L2E_OFFSET_MASK) >> s->cluster_bits;
|
||||||
if (!cluster_index) {
|
if (!cluster_index) {
|
||||||
/* unallocated */
|
/* unallocated */
|
||||||
@ -1838,26 +1876,11 @@ int qcow2_pre_write_overlap_check(BlockDriverState *bs, int ign, int64_t offset,
|
|||||||
return ret;
|
return ret;
|
||||||
} else if (ret > 0) {
|
} else if (ret > 0) {
|
||||||
int metadata_ol_bitnr = ffs(ret) - 1;
|
int metadata_ol_bitnr = ffs(ret) - 1;
|
||||||
char *message;
|
|
||||||
|
|
||||||
assert(metadata_ol_bitnr < QCOW2_OL_MAX_BITNR);
|
assert(metadata_ol_bitnr < QCOW2_OL_MAX_BITNR);
|
||||||
|
|
||||||
fprintf(stderr, "qcow2: Preventing invalid write on metadata (overlaps "
|
qcow2_signal_corruption(bs, true, offset, size, "Preventing invalid "
|
||||||
"with %s); image marked as corrupt.\n",
|
"write on metadata (overlaps with %s)",
|
||||||
metadata_ol_names[metadata_ol_bitnr]);
|
metadata_ol_names[metadata_ol_bitnr]);
|
||||||
message = g_strdup_printf("Prevented %s overwrite",
|
|
||||||
metadata_ol_names[metadata_ol_bitnr]);
|
|
||||||
qapi_event_send_block_image_corrupted(bdrv_get_device_name(bs),
|
|
||||||
message,
|
|
||||||
true,
|
|
||||||
offset,
|
|
||||||
true,
|
|
||||||
size,
|
|
||||||
&error_abort);
|
|
||||||
g_free(message);
|
|
||||||
|
|
||||||
qcow2_mark_corrupt(bs);
|
|
||||||
bs->drv = NULL; /* make BDS unusable */
|
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@
|
|||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "qapi/qmp/qbool.h"
|
#include "qapi/qmp/qbool.h"
|
||||||
#include "qapi/util.h"
|
#include "qapi/util.h"
|
||||||
|
#include "qapi/qmp/types.h"
|
||||||
|
#include "qapi-event.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "qemu/option_int.h"
|
#include "qemu/option_int.h"
|
||||||
|
|
||||||
@ -403,6 +405,12 @@ static QemuOptsList qcow2_runtime_opts = {
|
|||||||
.help = "Selects which overlap checks to perform from a range of "
|
.help = "Selects which overlap checks to perform from a range of "
|
||||||
"templates (none, constant, cached, all)",
|
"templates (none, constant, cached, all)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = QCOW2_OPT_OVERLAP_TEMPLATE,
|
||||||
|
.type = QEMU_OPT_STRING,
|
||||||
|
.help = "Selects which overlap checks to perform from a range of "
|
||||||
|
"templates (none, constant, cached, all)",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.name = QCOW2_OPT_OVERLAP_MAIN_HEADER,
|
.name = QCOW2_OPT_OVERLAP_MAIN_HEADER,
|
||||||
.type = QEMU_OPT_BOOL,
|
.type = QEMU_OPT_BOOL,
|
||||||
@ -536,11 +544,11 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
unsigned int len, i;
|
unsigned int len, i;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
QCowHeader header;
|
QCowHeader header;
|
||||||
QemuOpts *opts;
|
QemuOpts *opts = NULL;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
uint64_t ext_end;
|
uint64_t ext_end;
|
||||||
uint64_t l1_vm_state_index;
|
uint64_t l1_vm_state_index;
|
||||||
const char *opt_overlap_check;
|
const char *opt_overlap_check, *opt_overlap_check_template;
|
||||||
int overlap_check_template = 0;
|
int overlap_check_template = 0;
|
||||||
uint64_t l2_cache_size, refcount_cache_size;
|
uint64_t l2_cache_size, refcount_cache_size;
|
||||||
|
|
||||||
@ -920,7 +928,21 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
s->discard_passthrough[QCOW2_DISCARD_OTHER] =
|
s->discard_passthrough[QCOW2_DISCARD_OTHER] =
|
||||||
qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
|
qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
|
||||||
|
|
||||||
opt_overlap_check = qemu_opt_get(opts, "overlap-check") ?: "cached";
|
opt_overlap_check = qemu_opt_get(opts, QCOW2_OPT_OVERLAP);
|
||||||
|
opt_overlap_check_template = qemu_opt_get(opts, QCOW2_OPT_OVERLAP_TEMPLATE);
|
||||||
|
if (opt_overlap_check_template && opt_overlap_check &&
|
||||||
|
strcmp(opt_overlap_check_template, opt_overlap_check))
|
||||||
|
{
|
||||||
|
error_setg(errp, "Conflicting values for qcow2 options '"
|
||||||
|
QCOW2_OPT_OVERLAP "' ('%s') and '" QCOW2_OPT_OVERLAP_TEMPLATE
|
||||||
|
"' ('%s')", opt_overlap_check, opt_overlap_check_template);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (!opt_overlap_check) {
|
||||||
|
opt_overlap_check = opt_overlap_check_template ?: "cached";
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmp(opt_overlap_check, "none")) {
|
if (!strcmp(opt_overlap_check, "none")) {
|
||||||
overlap_check_template = 0;
|
overlap_check_template = 0;
|
||||||
} else if (!strcmp(opt_overlap_check, "constant")) {
|
} else if (!strcmp(opt_overlap_check, "constant")) {
|
||||||
@ -933,7 +955,6 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
error_setg(errp, "Unsupported value '%s' for qcow2 option "
|
error_setg(errp, "Unsupported value '%s' for qcow2 option "
|
||||||
"'overlap-check'. Allowed are either of the following: "
|
"'overlap-check'. Allowed are either of the following: "
|
||||||
"none, constant, cached, all", opt_overlap_check);
|
"none, constant, cached, all", opt_overlap_check);
|
||||||
qemu_opts_del(opts);
|
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
@ -948,6 +969,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
|
opts = NULL;
|
||||||
|
|
||||||
if (s->use_lazy_refcounts && s->qcow_version < 3) {
|
if (s->use_lazy_refcounts && s->qcow_version < 3) {
|
||||||
error_setg(errp, "Lazy refcounts require a qcow2 image with at least "
|
error_setg(errp, "Lazy refcounts require a qcow2 image with at least "
|
||||||
@ -965,6 +987,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
qemu_opts_del(opts);
|
||||||
g_free(s->unknown_header_fields);
|
g_free(s->unknown_header_fields);
|
||||||
cleanup_unknown_header_ext(bs);
|
cleanup_unknown_header_ext(bs);
|
||||||
qcow2_free_snapshots(bs);
|
qcow2_free_snapshots(bs);
|
||||||
@ -2529,6 +2552,52 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If offset or size are negative, respectively, they will not be included in
|
||||||
|
* the BLOCK_IMAGE_CORRUPTED event emitted.
|
||||||
|
* fatal will be ignored for read-only BDS; corruptions found there will always
|
||||||
|
* be considered non-fatal.
|
||||||
|
*/
|
||||||
|
void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
|
||||||
|
int64_t size, const char *message_format, ...)
|
||||||
|
{
|
||||||
|
BDRVQcowState *s = bs->opaque;
|
||||||
|
char *message;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
fatal = fatal && !bs->read_only;
|
||||||
|
|
||||||
|
if (s->signaled_corruption &&
|
||||||
|
(!fatal || (s->incompatible_features & QCOW2_INCOMPAT_CORRUPT)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
va_start(ap, message_format);
|
||||||
|
message = g_strdup_vprintf(message_format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
if (fatal) {
|
||||||
|
fprintf(stderr, "qcow2: Marking image as corrupt: %s; further "
|
||||||
|
"corruption events will be suppressed\n", message);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "qcow2: Image is corrupt: %s; further non-fatal "
|
||||||
|
"corruption events will be suppressed\n", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
qapi_event_send_block_image_corrupted(bdrv_get_device_name(bs), message,
|
||||||
|
offset >= 0, offset, size >= 0, size,
|
||||||
|
fatal, &error_abort);
|
||||||
|
g_free(message);
|
||||||
|
|
||||||
|
if (fatal) {
|
||||||
|
qcow2_mark_corrupt(bs);
|
||||||
|
bs->drv = NULL; /* make BDS unusable */
|
||||||
|
}
|
||||||
|
|
||||||
|
s->signaled_corruption = true;
|
||||||
|
}
|
||||||
|
|
||||||
static QemuOptsList qcow2_create_opts = {
|
static QemuOptsList qcow2_create_opts = {
|
||||||
.name = "qcow2-create-opts",
|
.name = "qcow2-create-opts",
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
|
.head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
|
||||||
|
@ -83,6 +83,7 @@
|
|||||||
#define QCOW2_OPT_DISCARD_SNAPSHOT "pass-discard-snapshot"
|
#define QCOW2_OPT_DISCARD_SNAPSHOT "pass-discard-snapshot"
|
||||||
#define QCOW2_OPT_DISCARD_OTHER "pass-discard-other"
|
#define QCOW2_OPT_DISCARD_OTHER "pass-discard-other"
|
||||||
#define QCOW2_OPT_OVERLAP "overlap-check"
|
#define QCOW2_OPT_OVERLAP "overlap-check"
|
||||||
|
#define QCOW2_OPT_OVERLAP_TEMPLATE "overlap-check.template"
|
||||||
#define QCOW2_OPT_OVERLAP_MAIN_HEADER "overlap-check.main-header"
|
#define QCOW2_OPT_OVERLAP_MAIN_HEADER "overlap-check.main-header"
|
||||||
#define QCOW2_OPT_OVERLAP_ACTIVE_L1 "overlap-check.active-l1"
|
#define QCOW2_OPT_OVERLAP_ACTIVE_L1 "overlap-check.active-l1"
|
||||||
#define QCOW2_OPT_OVERLAP_ACTIVE_L2 "overlap-check.active-l2"
|
#define QCOW2_OPT_OVERLAP_ACTIVE_L2 "overlap-check.active-l2"
|
||||||
@ -261,6 +262,7 @@ typedef struct BDRVQcowState {
|
|||||||
bool discard_passthrough[QCOW2_DISCARD_MAX];
|
bool discard_passthrough[QCOW2_DISCARD_MAX];
|
||||||
|
|
||||||
int overlap_check; /* bitmask of Qcow2MetadataOverlap values */
|
int overlap_check; /* bitmask of Qcow2MetadataOverlap values */
|
||||||
|
bool signaled_corruption;
|
||||||
|
|
||||||
uint64_t incompatible_features;
|
uint64_t incompatible_features;
|
||||||
uint64_t compatible_features;
|
uint64_t compatible_features;
|
||||||
@ -477,6 +479,10 @@ int qcow2_mark_corrupt(BlockDriverState *bs);
|
|||||||
int qcow2_mark_consistent(BlockDriverState *bs);
|
int qcow2_mark_consistent(BlockDriverState *bs);
|
||||||
int qcow2_update_header(BlockDriverState *bs);
|
int qcow2_update_header(BlockDriverState *bs);
|
||||||
|
|
||||||
|
void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
|
||||||
|
int64_t size, const char *message_format, ...)
|
||||||
|
GCC_FMT_ATTR(5, 6);
|
||||||
|
|
||||||
/* qcow2-refcount.c functions */
|
/* qcow2-refcount.c functions */
|
||||||
int qcow2_refcount_init(BlockDriverState *bs);
|
int qcow2_refcount_init(BlockDriverState *bs);
|
||||||
void qcow2_refcount_close(BlockDriverState *bs);
|
void qcow2_refcount_close(BlockDriverState *bs);
|
||||||
|
23
block/qed.c
23
block/qed.c
@ -18,22 +18,8 @@
|
|||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "migration/migration.h"
|
#include "migration/migration.h"
|
||||||
|
|
||||||
static void qed_aio_cancel(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
QEDAIOCB *acb = (QEDAIOCB *)blockacb;
|
|
||||||
AioContext *aio_context = bdrv_get_aio_context(blockacb->bs);
|
|
||||||
bool finished = false;
|
|
||||||
|
|
||||||
/* Wait for the request to finish */
|
|
||||||
acb->finished = &finished;
|
|
||||||
while (!finished) {
|
|
||||||
aio_poll(aio_context, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const AIOCBInfo qed_aiocb_info = {
|
static const AIOCBInfo qed_aiocb_info = {
|
||||||
.aiocb_size = sizeof(QEDAIOCB),
|
.aiocb_size = sizeof(QEDAIOCB),
|
||||||
.cancel = qed_aio_cancel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int bdrv_qed_probe(const uint8_t *buf, int buf_size,
|
static int bdrv_qed_probe(const uint8_t *buf, int buf_size,
|
||||||
@ -919,18 +905,12 @@ static void qed_aio_complete_bh(void *opaque)
|
|||||||
BlockDriverCompletionFunc *cb = acb->common.cb;
|
BlockDriverCompletionFunc *cb = acb->common.cb;
|
||||||
void *user_opaque = acb->common.opaque;
|
void *user_opaque = acb->common.opaque;
|
||||||
int ret = acb->bh_ret;
|
int ret = acb->bh_ret;
|
||||||
bool *finished = acb->finished;
|
|
||||||
|
|
||||||
qemu_bh_delete(acb->bh);
|
qemu_bh_delete(acb->bh);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
|
|
||||||
/* Invoke callback */
|
/* Invoke callback */
|
||||||
cb(user_opaque, ret);
|
cb(user_opaque, ret);
|
||||||
|
|
||||||
/* Signal cancel completion */
|
|
||||||
if (finished) {
|
|
||||||
*finished = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qed_aio_complete(QEDAIOCB *acb, int ret)
|
static void qed_aio_complete(QEDAIOCB *acb, int ret)
|
||||||
@ -1397,7 +1377,6 @@ static BlockDriverAIOCB *qed_aio_setup(BlockDriverState *bs,
|
|||||||
opaque, flags);
|
opaque, flags);
|
||||||
|
|
||||||
acb->flags = flags;
|
acb->flags = flags;
|
||||||
acb->finished = NULL;
|
|
||||||
acb->qiov = qiov;
|
acb->qiov = qiov;
|
||||||
acb->qiov_offset = 0;
|
acb->qiov_offset = 0;
|
||||||
acb->cur_pos = (uint64_t)sector_num * BDRV_SECTOR_SIZE;
|
acb->cur_pos = (uint64_t)sector_num * BDRV_SECTOR_SIZE;
|
||||||
|
@ -138,16 +138,15 @@ static void quorum_aio_cancel(BlockDriverAIOCB *blockacb)
|
|||||||
|
|
||||||
/* cancel all callbacks */
|
/* cancel all callbacks */
|
||||||
for (i = 0; i < s->num_children; i++) {
|
for (i = 0; i < s->num_children; i++) {
|
||||||
bdrv_aio_cancel(acb->qcrs[i].aiocb);
|
if (acb->qcrs[i].aiocb) {
|
||||||
|
bdrv_aio_cancel_async(acb->qcrs[i].aiocb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(acb->qcrs);
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static AIOCBInfo quorum_aiocb_info = {
|
static AIOCBInfo quorum_aiocb_info = {
|
||||||
.aiocb_size = sizeof(QuorumAIOCB),
|
.aiocb_size = sizeof(QuorumAIOCB),
|
||||||
.cancel = quorum_aio_cancel,
|
.cancel_async = quorum_aio_cancel,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void quorum_aio_finalize(QuorumAIOCB *acb)
|
static void quorum_aio_finalize(QuorumAIOCB *acb)
|
||||||
@ -169,7 +168,7 @@ static void quorum_aio_finalize(QuorumAIOCB *acb)
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_free(acb->qcrs);
|
g_free(acb->qcrs);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool quorum_sha256_compare(QuorumVoteValue *a, QuorumVoteValue *b)
|
static bool quorum_sha256_compare(QuorumVoteValue *a, QuorumVoteValue *b)
|
||||||
|
25
block/rbd.c
25
block/rbd.c
@ -77,7 +77,6 @@ typedef struct RBDAIOCB {
|
|||||||
int64_t sector_num;
|
int64_t sector_num;
|
||||||
int error;
|
int error;
|
||||||
struct BDRVRBDState *s;
|
struct BDRVRBDState *s;
|
||||||
int cancelled;
|
|
||||||
int status;
|
int status;
|
||||||
} RBDAIOCB;
|
} RBDAIOCB;
|
||||||
|
|
||||||
@ -408,9 +407,7 @@ static void qemu_rbd_complete_aio(RADOSCB *rcb)
|
|||||||
acb->common.cb(acb->common.opaque, (acb->ret > 0 ? 0 : acb->ret));
|
acb->common.cb(acb->common.opaque, (acb->ret > 0 ? 0 : acb->ret));
|
||||||
acb->status = 0;
|
acb->status = 0;
|
||||||
|
|
||||||
if (!acb->cancelled) {
|
qemu_aio_unref(acb);
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO Convert to fine grained options */
|
/* TODO Convert to fine grained options */
|
||||||
@ -539,25 +536,8 @@ static void qemu_rbd_close(BlockDriverState *bs)
|
|||||||
rados_shutdown(s->cluster);
|
rados_shutdown(s->cluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Cancel aio. Since we don't reference acb in a non qemu threads,
|
|
||||||
* it is safe to access it here.
|
|
||||||
*/
|
|
||||||
static void qemu_rbd_aio_cancel(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
RBDAIOCB *acb = (RBDAIOCB *) blockacb;
|
|
||||||
acb->cancelled = 1;
|
|
||||||
|
|
||||||
while (acb->status == -EINPROGRESS) {
|
|
||||||
aio_poll(bdrv_get_aio_context(acb->common.bs), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const AIOCBInfo rbd_aiocb_info = {
|
static const AIOCBInfo rbd_aiocb_info = {
|
||||||
.aiocb_size = sizeof(RBDAIOCB),
|
.aiocb_size = sizeof(RBDAIOCB),
|
||||||
.cancel = qemu_rbd_aio_cancel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void rbd_finish_bh(void *opaque)
|
static void rbd_finish_bh(void *opaque)
|
||||||
@ -640,7 +620,6 @@ static BlockDriverAIOCB *rbd_start_aio(BlockDriverState *bs,
|
|||||||
acb->ret = 0;
|
acb->ret = 0;
|
||||||
acb->error = 0;
|
acb->error = 0;
|
||||||
acb->s = s;
|
acb->s = s;
|
||||||
acb->cancelled = 0;
|
|
||||||
acb->bh = NULL;
|
acb->bh = NULL;
|
||||||
acb->status = -EINPROGRESS;
|
acb->status = -EINPROGRESS;
|
||||||
|
|
||||||
@ -692,7 +671,7 @@ failed_completion:
|
|||||||
failed:
|
failed:
|
||||||
g_free(rcb);
|
g_free(rcb);
|
||||||
qemu_vfree(acb->bounce);
|
qemu_vfree(acb->bounce);
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +315,6 @@ struct SheepdogAIOCB {
|
|||||||
void (*aio_done_func)(SheepdogAIOCB *);
|
void (*aio_done_func)(SheepdogAIOCB *);
|
||||||
|
|
||||||
bool cancelable;
|
bool cancelable;
|
||||||
bool *finished;
|
|
||||||
int nr_pending;
|
int nr_pending;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -446,10 +445,7 @@ static inline void free_aio_req(BDRVSheepdogState *s, AIOReq *aio_req)
|
|||||||
static void coroutine_fn sd_finish_aiocb(SheepdogAIOCB *acb)
|
static void coroutine_fn sd_finish_aiocb(SheepdogAIOCB *acb)
|
||||||
{
|
{
|
||||||
qemu_coroutine_enter(acb->coroutine, NULL);
|
qemu_coroutine_enter(acb->coroutine, NULL);
|
||||||
if (acb->finished) {
|
qemu_aio_unref(acb);
|
||||||
*acb->finished = true;
|
|
||||||
}
|
|
||||||
qemu_aio_release(acb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -482,36 +478,33 @@ static void sd_aio_cancel(BlockDriverAIOCB *blockacb)
|
|||||||
SheepdogAIOCB *acb = (SheepdogAIOCB *)blockacb;
|
SheepdogAIOCB *acb = (SheepdogAIOCB *)blockacb;
|
||||||
BDRVSheepdogState *s = acb->common.bs->opaque;
|
BDRVSheepdogState *s = acb->common.bs->opaque;
|
||||||
AIOReq *aioreq, *next;
|
AIOReq *aioreq, *next;
|
||||||
bool finished = false;
|
|
||||||
|
|
||||||
acb->finished = &finished;
|
if (sd_acb_cancelable(acb)) {
|
||||||
while (!finished) {
|
/* Remove outstanding requests from pending and failed queues. */
|
||||||
if (sd_acb_cancelable(acb)) {
|
QLIST_FOREACH_SAFE(aioreq, &s->pending_aio_head, aio_siblings,
|
||||||
/* Remove outstanding requests from pending and failed queues. */
|
next) {
|
||||||
QLIST_FOREACH_SAFE(aioreq, &s->pending_aio_head, aio_siblings,
|
if (aioreq->aiocb == acb) {
|
||||||
next) {
|
free_aio_req(s, aioreq);
|
||||||
if (aioreq->aiocb == acb) {
|
|
||||||
free_aio_req(s, aioreq);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
QLIST_FOREACH_SAFE(aioreq, &s->failed_aio_head, aio_siblings,
|
|
||||||
next) {
|
|
||||||
if (aioreq->aiocb == acb) {
|
|
||||||
free_aio_req(s, aioreq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(acb->nr_pending == 0);
|
|
||||||
sd_finish_aiocb(acb);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
aio_poll(s->aio_context, true);
|
QLIST_FOREACH_SAFE(aioreq, &s->failed_aio_head, aio_siblings,
|
||||||
|
next) {
|
||||||
|
if (aioreq->aiocb == acb) {
|
||||||
|
free_aio_req(s, aioreq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(acb->nr_pending == 0);
|
||||||
|
if (acb->common.cb) {
|
||||||
|
acb->common.cb(acb->common.opaque, -ECANCELED);
|
||||||
|
}
|
||||||
|
sd_finish_aiocb(acb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const AIOCBInfo sd_aiocb_info = {
|
static const AIOCBInfo sd_aiocb_info = {
|
||||||
.aiocb_size = sizeof(SheepdogAIOCB),
|
.aiocb_size = sizeof(SheepdogAIOCB),
|
||||||
.cancel = sd_aio_cancel,
|
.cancel_async = sd_aio_cancel,
|
||||||
};
|
};
|
||||||
|
|
||||||
static SheepdogAIOCB *sd_aio_setup(BlockDriverState *bs, QEMUIOVector *qiov,
|
static SheepdogAIOCB *sd_aio_setup(BlockDriverState *bs, QEMUIOVector *qiov,
|
||||||
@ -528,7 +521,6 @@ static SheepdogAIOCB *sd_aio_setup(BlockDriverState *bs, QEMUIOVector *qiov,
|
|||||||
|
|
||||||
acb->aio_done_func = NULL;
|
acb->aio_done_func = NULL;
|
||||||
acb->cancelable = true;
|
acb->cancelable = true;
|
||||||
acb->finished = NULL;
|
|
||||||
acb->coroutine = qemu_coroutine_self();
|
acb->coroutine = qemu_coroutine_self();
|
||||||
acb->ret = 0;
|
acb->ret = 0;
|
||||||
acb->nr_pending = 0;
|
acb->nr_pending = 0;
|
||||||
@ -2138,7 +2130,7 @@ static coroutine_fn int sd_co_writev(BlockDriverState *bs, int64_t sector_num,
|
|||||||
|
|
||||||
ret = sd_co_rw_vector(acb);
|
ret = sd_co_rw_vector(acb);
|
||||||
if (ret <= 0) {
|
if (ret <= 0) {
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2159,7 +2151,7 @@ static coroutine_fn int sd_co_readv(BlockDriverState *bs, int64_t sector_num,
|
|||||||
|
|
||||||
ret = sd_co_rw_vector(acb);
|
ret = sd_co_rw_vector(acb);
|
||||||
if (ret <= 0) {
|
if (ret <= 0) {
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2518,7 +2510,7 @@ static coroutine_fn int sd_co_discard(BlockDriverState *bs, int64_t sector_num,
|
|||||||
|
|
||||||
ret = sd_co_rw_vector(acb);
|
ret = sd_co_rw_vector(acb);
|
||||||
if (ret <= 0) {
|
if (ret <= 0) {
|
||||||
qemu_aio_release(acb);
|
qemu_aio_unref(acb);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
block/vhdx.c
19
block/vhdx.c
@ -99,7 +99,8 @@ static const MSGUID logical_sector_guid = { .data1 = 0x8141bf1d,
|
|||||||
/* Each parent type must have a valid GUID; this is for parent images
|
/* Each parent type must have a valid GUID; this is for parent images
|
||||||
* of type 'VHDX'. If we were to allow e.g. a QCOW2 parent, we would
|
* of type 'VHDX'. If we were to allow e.g. a QCOW2 parent, we would
|
||||||
* need to make up our own QCOW2 GUID type */
|
* need to make up our own QCOW2 GUID type */
|
||||||
static const MSGUID parent_vhdx_guid = { .data1 = 0xb04aefb7,
|
static const MSGUID parent_vhdx_guid __attribute__((unused))
|
||||||
|
= { .data1 = 0xb04aefb7,
|
||||||
.data2 = 0xd19e,
|
.data2 = 0xd19e,
|
||||||
.data3 = 0x4a81,
|
.data3 = 0x4a81,
|
||||||
.data4 = { 0xb7, 0x89, 0x25, 0xb8,
|
.data4 = { 0xb7, 0x89, 0x25, 0xb8,
|
||||||
@ -1407,6 +1408,12 @@ exit:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define VHDX_METADATA_ENTRY_BUFFER_SIZE \
|
||||||
|
(sizeof(VHDXFileParameters) +\
|
||||||
|
sizeof(VHDXVirtualDiskSize) +\
|
||||||
|
sizeof(VHDXPage83Data) +\
|
||||||
|
sizeof(VHDXVirtualDiskLogicalSectorSize) +\
|
||||||
|
sizeof(VHDXVirtualDiskPhysicalSectorSize))
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create the Metadata entries.
|
* Create the Metadata entries.
|
||||||
@ -1445,11 +1452,7 @@ static int vhdx_create_new_metadata(BlockDriverState *bs,
|
|||||||
VHDXVirtualDiskLogicalSectorSize *mt_log_sector_size;
|
VHDXVirtualDiskLogicalSectorSize *mt_log_sector_size;
|
||||||
VHDXVirtualDiskPhysicalSectorSize *mt_phys_sector_size;
|
VHDXVirtualDiskPhysicalSectorSize *mt_phys_sector_size;
|
||||||
|
|
||||||
entry_buffer = g_malloc0(sizeof(VHDXFileParameters) +
|
entry_buffer = g_malloc0(VHDX_METADATA_ENTRY_BUFFER_SIZE);
|
||||||
sizeof(VHDXVirtualDiskSize) +
|
|
||||||
sizeof(VHDXPage83Data) +
|
|
||||||
sizeof(VHDXVirtualDiskLogicalSectorSize) +
|
|
||||||
sizeof(VHDXVirtualDiskPhysicalSectorSize));
|
|
||||||
|
|
||||||
mt_file_params = entry_buffer;
|
mt_file_params = entry_buffer;
|
||||||
offset += sizeof(VHDXFileParameters);
|
offset += sizeof(VHDXFileParameters);
|
||||||
@ -1530,7 +1533,7 @@ static int vhdx_create_new_metadata(BlockDriverState *bs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = bdrv_pwrite(bs, metadata_offset + (64 * KiB), entry_buffer,
|
ret = bdrv_pwrite(bs, metadata_offset + (64 * KiB), entry_buffer,
|
||||||
VHDX_HEADER_BLOCK_SIZE);
|
VHDX_METADATA_ENTRY_BUFFER_SIZE);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
@ -1725,7 +1728,6 @@ static int vhdx_create_new_region_table(BlockDriverState *bs,
|
|||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
g_free(s);
|
g_free(s);
|
||||||
g_free(buffer);
|
g_free(buffer);
|
||||||
@ -1876,7 +1878,6 @@ static int vhdx_create(const char *filename, QemuOpts *opts, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
delete_and_exit:
|
delete_and_exit:
|
||||||
bdrv_unref(bs);
|
bdrv_unref(bs);
|
||||||
exit:
|
exit:
|
||||||
|
@ -88,7 +88,7 @@ static void win32_aio_process_completion(QEMUWin32AIOState *s,
|
|||||||
|
|
||||||
|
|
||||||
waiocb->common.cb(waiocb->common.opaque, ret);
|
waiocb->common.cb(waiocb->common.opaque, ret);
|
||||||
qemu_aio_release(waiocb);
|
qemu_aio_unref(waiocb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void win32_aio_completion_cb(EventNotifier *e)
|
static void win32_aio_completion_cb(EventNotifier *e)
|
||||||
@ -106,22 +106,8 @@ static void win32_aio_completion_cb(EventNotifier *e)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void win32_aio_cancel(BlockDriverAIOCB *blockacb)
|
|
||||||
{
|
|
||||||
QEMUWin32AIOCB *waiocb = (QEMUWin32AIOCB *)blockacb;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* CancelIoEx is only supported in Vista and newer. For now, just
|
|
||||||
* wait for completion.
|
|
||||||
*/
|
|
||||||
while (!HasOverlappedIoCompleted(&waiocb->ov)) {
|
|
||||||
aio_poll(bdrv_get_aio_context(blockacb->bs), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const AIOCBInfo win32_aiocb_info = {
|
static const AIOCBInfo win32_aiocb_info = {
|
||||||
.aiocb_size = sizeof(QEMUWin32AIOCB),
|
.aiocb_size = sizeof(QEMUWin32AIOCB),
|
||||||
.cancel = win32_aio_cancel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockDriverAIOCB *win32_aio_submit(BlockDriverState *bs,
|
BlockDriverAIOCB *win32_aio_submit(BlockDriverState *bs,
|
||||||
@ -172,7 +158,7 @@ BlockDriverAIOCB *win32_aio_submit(BlockDriverState *bs,
|
|||||||
out_dec_count:
|
out_dec_count:
|
||||||
aio->count--;
|
aio->count--;
|
||||||
out:
|
out:
|
||||||
qemu_aio_release(waiocb);
|
qemu_aio_unref(waiocb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
configure
vendored
21
configure
vendored
@ -327,7 +327,6 @@ glusterfs=""
|
|||||||
glusterfs_discard="no"
|
glusterfs_discard="no"
|
||||||
glusterfs_zerofill="no"
|
glusterfs_zerofill="no"
|
||||||
archipelago=""
|
archipelago=""
|
||||||
virtio_blk_data_plane=""
|
|
||||||
gtk=""
|
gtk=""
|
||||||
gtkabi=""
|
gtkabi=""
|
||||||
vte=""
|
vte=""
|
||||||
@ -1093,9 +1092,8 @@ for opt do
|
|||||||
;;
|
;;
|
||||||
--enable-archipelago) archipelago="yes"
|
--enable-archipelago) archipelago="yes"
|
||||||
;;
|
;;
|
||||||
--disable-virtio-blk-data-plane) virtio_blk_data_plane="no"
|
--disable-virtio-blk-data-plane|--enable-virtio-blk-data-plane)
|
||||||
;;
|
echo "$0: $opt is obsolete, virtio-blk data-plane is always on" >&2
|
||||||
--enable-virtio-blk-data-plane) virtio_blk_data_plane="yes"
|
|
||||||
;;
|
;;
|
||||||
--disable-gtk) gtk="no"
|
--disable-gtk) gtk="no"
|
||||||
;;
|
;;
|
||||||
@ -2943,16 +2941,6 @@ else
|
|||||||
tpm_passthrough=no
|
tpm_passthrough=no
|
||||||
fi
|
fi
|
||||||
|
|
||||||
##########################################
|
|
||||||
# adjust virtio-blk-data-plane based on linux-aio
|
|
||||||
|
|
||||||
if test "$virtio_blk_data_plane" = "yes" -a \
|
|
||||||
"$linux_aio" != "yes" ; then
|
|
||||||
error_exit "virtio-blk-data-plane requires Linux AIO, please try --enable-linux-aio"
|
|
||||||
elif test -z "$virtio_blk_data_plane" ; then
|
|
||||||
virtio_blk_data_plane=$linux_aio
|
|
||||||
fi
|
|
||||||
|
|
||||||
##########################################
|
##########################################
|
||||||
# attr probe
|
# attr probe
|
||||||
|
|
||||||
@ -4327,7 +4315,6 @@ echo "coroutine backend $coroutine"
|
|||||||
echo "coroutine pool $coroutine_pool"
|
echo "coroutine pool $coroutine_pool"
|
||||||
echo "GlusterFS support $glusterfs"
|
echo "GlusterFS support $glusterfs"
|
||||||
echo "Archipelago support $archipelago"
|
echo "Archipelago support $archipelago"
|
||||||
echo "virtio-blk-data-plane $virtio_blk_data_plane"
|
|
||||||
echo "gcov $gcov_tool"
|
echo "gcov $gcov_tool"
|
||||||
echo "gcov enabled $gcov"
|
echo "gcov enabled $gcov"
|
||||||
echo "TPM support $tpm"
|
echo "TPM support $tpm"
|
||||||
@ -4789,10 +4776,6 @@ if test "$quorum" = "yes" ; then
|
|||||||
echo "CONFIG_QUORUM=y" >> $config_host_mak
|
echo "CONFIG_QUORUM=y" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test "$virtio_blk_data_plane" = "yes" ; then
|
|
||||||
echo 'CONFIG_VIRTIO_BLK_DATA_PLANE=$(CONFIG_VIRTIO)' >> $config_host_mak
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test "$vhdx" = "yes" ; then
|
if test "$vhdx" = "yes" ; then
|
||||||
echo "CONFIG_VHDX=y" >> $config_host_mak
|
echo "CONFIG_VHDX=y" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
|
@ -73,7 +73,6 @@ typedef struct {
|
|||||||
QEMUSGList *sg;
|
QEMUSGList *sg;
|
||||||
uint64_t sector_num;
|
uint64_t sector_num;
|
||||||
DMADirection dir;
|
DMADirection dir;
|
||||||
bool in_cancel;
|
|
||||||
int sg_cur_index;
|
int sg_cur_index;
|
||||||
dma_addr_t sg_cur_byte;
|
dma_addr_t sg_cur_byte;
|
||||||
QEMUIOVector iov;
|
QEMUIOVector iov;
|
||||||
@ -125,12 +124,7 @@ static void dma_complete(DMAAIOCB *dbs, int ret)
|
|||||||
qemu_bh_delete(dbs->bh);
|
qemu_bh_delete(dbs->bh);
|
||||||
dbs->bh = NULL;
|
dbs->bh = NULL;
|
||||||
}
|
}
|
||||||
if (!dbs->in_cancel) {
|
qemu_aio_unref(dbs);
|
||||||
/* Requests may complete while dma_aio_cancel is in progress. In
|
|
||||||
* this case, the AIOCB should not be released because it is still
|
|
||||||
* referenced by dma_aio_cancel. */
|
|
||||||
qemu_aio_release(dbs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dma_bdrv_cb(void *opaque, int ret)
|
static void dma_bdrv_cb(void *opaque, int ret)
|
||||||
@ -186,19 +180,14 @@ static void dma_aio_cancel(BlockDriverAIOCB *acb)
|
|||||||
trace_dma_aio_cancel(dbs);
|
trace_dma_aio_cancel(dbs);
|
||||||
|
|
||||||
if (dbs->acb) {
|
if (dbs->acb) {
|
||||||
BlockDriverAIOCB *acb = dbs->acb;
|
bdrv_aio_cancel_async(dbs->acb);
|
||||||
dbs->acb = NULL;
|
|
||||||
dbs->in_cancel = true;
|
|
||||||
bdrv_aio_cancel(acb);
|
|
||||||
dbs->in_cancel = false;
|
|
||||||
}
|
}
|
||||||
dbs->common.cb = NULL;
|
|
||||||
dma_complete(dbs, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static const AIOCBInfo dma_aiocb_info = {
|
static const AIOCBInfo dma_aiocb_info = {
|
||||||
.aiocb_size = sizeof(DMAAIOCB),
|
.aiocb_size = sizeof(DMAAIOCB),
|
||||||
.cancel = dma_aio_cancel,
|
.cancel_async = dma_aio_cancel,
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockDriverAIOCB *dma_bdrv_io(
|
BlockDriverAIOCB *dma_bdrv_io(
|
||||||
@ -217,7 +206,6 @@ BlockDriverAIOCB *dma_bdrv_io(
|
|||||||
dbs->sg_cur_index = 0;
|
dbs->sg_cur_index = 0;
|
||||||
dbs->sg_cur_byte = 0;
|
dbs->sg_cur_byte = 0;
|
||||||
dbs->dir = dir;
|
dbs->dir = dir;
|
||||||
dbs->in_cancel = false;
|
|
||||||
dbs->io_func = io_func;
|
dbs->io_func = io_func;
|
||||||
dbs->bh = NULL;
|
dbs->bh = NULL;
|
||||||
qemu_iovec_init(&dbs->iov, sg->nsg);
|
qemu_iovec_init(&dbs->iov, sg->nsg);
|
||||||
|
@ -125,7 +125,8 @@ If a fuzzer configuration is specified, then it has the next interpretation:
|
|||||||
will be always fuzzed for every test. This case is useful for regression
|
will be always fuzzed for every test. This case is useful for regression
|
||||||
testing.
|
testing.
|
||||||
|
|
||||||
For now only header fields, header extensions and L1/L2 tables are generated.
|
The generator can create header fields, header extensions, L1/L2 tables and
|
||||||
|
refcount table and blocks.
|
||||||
|
|
||||||
Module interfaces
|
Module interfaces
|
||||||
-----------------
|
-----------------
|
||||||
|
2
hmp.c
2
hmp.c
@ -679,6 +679,8 @@ void hmp_info_block_jobs(Monitor *mon, const QDict *qdict)
|
|||||||
}
|
}
|
||||||
list = list->next;
|
list = list->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qapi_free_BlockJobInfoList(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
void hmp_info_tpm(Monitor *mon, const QDict *qdict)
|
void hmp_info_tpm(Monitor *mon, const QDict *qdict)
|
||||||
|
@ -12,4 +12,4 @@ common-obj-$(CONFIG_NVME_PCI) += nvme.o
|
|||||||
obj-$(CONFIG_SH4) += tc58128.o
|
obj-$(CONFIG_SH4) += tc58128.o
|
||||||
|
|
||||||
obj-$(CONFIG_VIRTIO) += virtio-blk.o
|
obj-$(CONFIG_VIRTIO) += virtio-blk.o
|
||||||
obj-$(CONFIG_VIRTIO_BLK_DATA_PLANE) += dataplane/
|
obj-$(CONFIG_VIRTIO) += dataplane/
|
||||||
|
@ -18,10 +18,8 @@
|
|||||||
#include "hw/block/block.h"
|
#include "hw/block/block.h"
|
||||||
#include "sysemu/blockdev.h"
|
#include "sysemu/blockdev.h"
|
||||||
#include "hw/virtio/virtio-blk.h"
|
#include "hw/virtio/virtio-blk.h"
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
#include "dataplane/virtio-blk.h"
|
||||||
# include "dataplane/virtio-blk.h"
|
#include "migration/migration.h"
|
||||||
# include "migration/migration.h"
|
|
||||||
#endif
|
|
||||||
#include "block/scsi.h"
|
#include "block/scsi.h"
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
# include <scsi/sg.h>
|
# include <scsi/sg.h>
|
||||||
@ -435,7 +433,6 @@ static void virtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq)
|
|||||||
.num_writes = 0,
|
.num_writes = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
/* Some guests kick before setting VIRTIO_CONFIG_S_DRIVER_OK so start
|
/* Some guests kick before setting VIRTIO_CONFIG_S_DRIVER_OK so start
|
||||||
* dataplane here instead of waiting for .set_status().
|
* dataplane here instead of waiting for .set_status().
|
||||||
*/
|
*/
|
||||||
@ -443,7 +440,6 @@ static void virtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq)
|
|||||||
virtio_blk_data_plane_start(s->dataplane);
|
virtio_blk_data_plane_start(s->dataplane);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
while ((req = virtio_blk_get_request(s))) {
|
while ((req = virtio_blk_get_request(s))) {
|
||||||
virtio_blk_handle_request(req, &mrb);
|
virtio_blk_handle_request(req, &mrb);
|
||||||
@ -500,11 +496,9 @@ static void virtio_blk_reset(VirtIODevice *vdev)
|
|||||||
{
|
{
|
||||||
VirtIOBlock *s = VIRTIO_BLK(vdev);
|
VirtIOBlock *s = VIRTIO_BLK(vdev);
|
||||||
|
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
if (s->dataplane) {
|
if (s->dataplane) {
|
||||||
virtio_blk_data_plane_stop(s->dataplane);
|
virtio_blk_data_plane_stop(s->dataplane);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This should cancel pending requests, but can't do nicely until there
|
* This should cancel pending requests, but can't do nicely until there
|
||||||
@ -594,12 +588,10 @@ static void virtio_blk_set_status(VirtIODevice *vdev, uint8_t status)
|
|||||||
VirtIOBlock *s = VIRTIO_BLK(vdev);
|
VirtIOBlock *s = VIRTIO_BLK(vdev);
|
||||||
uint32_t features;
|
uint32_t features;
|
||||||
|
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
if (s->dataplane && !(status & (VIRTIO_CONFIG_S_DRIVER |
|
if (s->dataplane && !(status & (VIRTIO_CONFIG_S_DRIVER |
|
||||||
VIRTIO_CONFIG_S_DRIVER_OK))) {
|
VIRTIO_CONFIG_S_DRIVER_OK))) {
|
||||||
virtio_blk_data_plane_stop(s->dataplane);
|
virtio_blk_data_plane_stop(s->dataplane);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!(status & VIRTIO_CONFIG_S_DRIVER_OK)) {
|
if (!(status & VIRTIO_CONFIG_S_DRIVER_OK)) {
|
||||||
return;
|
return;
|
||||||
@ -694,7 +686,6 @@ static const BlockDevOps virtio_block_ops = {
|
|||||||
.resize_cb = virtio_blk_resize,
|
.resize_cb = virtio_blk_resize,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
/* Disable dataplane thread during live migration since it does not
|
/* Disable dataplane thread during live migration since it does not
|
||||||
* update the dirty memory bitmap yet.
|
* update the dirty memory bitmap yet.
|
||||||
*/
|
*/
|
||||||
@ -725,7 +716,6 @@ static void virtio_blk_migration_state_changed(Notifier *notifier, void *data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_VIRTIO_BLK_DATA_PLANE */
|
|
||||||
|
|
||||||
static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
|
static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
|
||||||
{
|
{
|
||||||
@ -762,7 +752,6 @@ static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
|
|||||||
|
|
||||||
s->vq = virtio_add_queue(vdev, 128, virtio_blk_handle_output);
|
s->vq = virtio_add_queue(vdev, 128, virtio_blk_handle_output);
|
||||||
s->complete_request = virtio_blk_complete_request;
|
s->complete_request = virtio_blk_complete_request;
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
virtio_blk_data_plane_create(vdev, blk, &s->dataplane, &err);
|
virtio_blk_data_plane_create(vdev, blk, &s->dataplane, &err);
|
||||||
if (err != NULL) {
|
if (err != NULL) {
|
||||||
error_propagate(errp, err);
|
error_propagate(errp, err);
|
||||||
@ -771,7 +760,6 @@ static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
|
|||||||
}
|
}
|
||||||
s->migration_state_notifier.notify = virtio_blk_migration_state_changed;
|
s->migration_state_notifier.notify = virtio_blk_migration_state_changed;
|
||||||
add_migration_state_change_notifier(&s->migration_state_notifier);
|
add_migration_state_change_notifier(&s->migration_state_notifier);
|
||||||
#endif
|
|
||||||
|
|
||||||
s->change = qemu_add_vm_change_state_handler(virtio_blk_dma_restart_cb, s);
|
s->change = qemu_add_vm_change_state_handler(virtio_blk_dma_restart_cb, s);
|
||||||
register_savevm(dev, "virtio-blk", virtio_blk_id++, 2,
|
register_savevm(dev, "virtio-blk", virtio_blk_id++, 2,
|
||||||
@ -789,11 +777,9 @@ static void virtio_blk_device_unrealize(DeviceState *dev, Error **errp)
|
|||||||
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
||||||
VirtIOBlock *s = VIRTIO_BLK(dev);
|
VirtIOBlock *s = VIRTIO_BLK(dev);
|
||||||
|
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
remove_migration_state_change_notifier(&s->migration_state_notifier);
|
remove_migration_state_change_notifier(&s->migration_state_notifier);
|
||||||
virtio_blk_data_plane_destroy(s->dataplane);
|
virtio_blk_data_plane_destroy(s->dataplane);
|
||||||
s->dataplane = NULL;
|
s->dataplane = NULL;
|
||||||
#endif
|
|
||||||
qemu_del_vm_change_state_handler(s->change);
|
qemu_del_vm_change_state_handler(s->change);
|
||||||
unregister_savevm(dev, "virtio-blk", s);
|
unregister_savevm(dev, "virtio-blk", s);
|
||||||
blockdev_mark_auto_del(s->bs);
|
blockdev_mark_auto_del(s->bs);
|
||||||
@ -818,9 +804,7 @@ static Property virtio_blk_properties[] = {
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
DEFINE_PROP_BIT("scsi", VirtIOBlock, blk.scsi, 0, true),
|
DEFINE_PROP_BIT("scsi", VirtIOBlock, blk.scsi, 0, true),
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
DEFINE_PROP_BIT("x-data-plane", VirtIOBlock, blk.data_plane, 0, false),
|
DEFINE_PROP_BIT("x-data-plane", VirtIOBlock, blk.data_plane, 0, false),
|
||||||
#endif
|
|
||||||
DEFINE_PROP_END_OF_LIST(),
|
DEFINE_PROP_END_OF_LIST(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,8 +78,7 @@ static uint32_t ahci_port_read(AHCIState *s, int port, int offset)
|
|||||||
val = pr->cmd;
|
val = pr->cmd;
|
||||||
break;
|
break;
|
||||||
case PORT_TFDATA:
|
case PORT_TFDATA:
|
||||||
val = ((uint16_t)s->dev[port].port.ifs[0].error << 8) |
|
val = pr->tfdata;
|
||||||
s->dev[port].port.ifs[0].status;
|
|
||||||
break;
|
break;
|
||||||
case PORT_SIG:
|
case PORT_SIG:
|
||||||
val = pr->sig;
|
val = pr->sig;
|
||||||
@ -251,14 +250,13 @@ static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
|
|||||||
check_cmd(s, port);
|
check_cmd(s, port);
|
||||||
break;
|
break;
|
||||||
case PORT_TFDATA:
|
case PORT_TFDATA:
|
||||||
s->dev[port].port.ifs[0].error = (val >> 8) & 0xff;
|
/* Read Only. */
|
||||||
s->dev[port].port.ifs[0].status = val & 0xff;
|
|
||||||
break;
|
break;
|
||||||
case PORT_SIG:
|
case PORT_SIG:
|
||||||
pr->sig = val;
|
/* Read Only */
|
||||||
break;
|
break;
|
||||||
case PORT_SCR_STAT:
|
case PORT_SCR_STAT:
|
||||||
pr->scr_stat = val;
|
/* Read Only */
|
||||||
break;
|
break;
|
||||||
case PORT_SCR_CTL:
|
case PORT_SCR_CTL:
|
||||||
if (((pr->scr_ctl & AHCI_SCR_SCTL_DET) == 1) &&
|
if (((pr->scr_ctl & AHCI_SCR_SCTL_DET) == 1) &&
|
||||||
@ -497,6 +495,8 @@ static void ahci_reset_port(AHCIState *s, int port)
|
|||||||
pr->scr_stat = 0;
|
pr->scr_stat = 0;
|
||||||
pr->scr_err = 0;
|
pr->scr_err = 0;
|
||||||
pr->scr_act = 0;
|
pr->scr_act = 0;
|
||||||
|
pr->tfdata = 0x7F;
|
||||||
|
pr->sig = 0xFFFFFFFF;
|
||||||
d->busy_slot = -1;
|
d->busy_slot = -1;
|
||||||
d->init_d2h_sent = false;
|
d->init_d2h_sent = false;
|
||||||
|
|
||||||
@ -528,16 +528,16 @@ static void ahci_reset_port(AHCIState *s, int port)
|
|||||||
|
|
||||||
s->dev[port].port_state = STATE_RUN;
|
s->dev[port].port_state = STATE_RUN;
|
||||||
if (!ide_state->bs) {
|
if (!ide_state->bs) {
|
||||||
s->dev[port].port_regs.sig = 0;
|
pr->sig = 0;
|
||||||
ide_state->status = SEEK_STAT | WRERR_STAT;
|
ide_state->status = SEEK_STAT | WRERR_STAT;
|
||||||
} else if (ide_state->drive_kind == IDE_CD) {
|
} else if (ide_state->drive_kind == IDE_CD) {
|
||||||
s->dev[port].port_regs.sig = SATA_SIGNATURE_CDROM;
|
pr->sig = SATA_SIGNATURE_CDROM;
|
||||||
ide_state->lcyl = 0x14;
|
ide_state->lcyl = 0x14;
|
||||||
ide_state->hcyl = 0xeb;
|
ide_state->hcyl = 0xeb;
|
||||||
DPRINTF(port, "set lcyl = %d\n", ide_state->lcyl);
|
DPRINTF(port, "set lcyl = %d\n", ide_state->lcyl);
|
||||||
ide_state->status = SEEK_STAT | WRERR_STAT | READY_STAT;
|
ide_state->status = SEEK_STAT | WRERR_STAT | READY_STAT;
|
||||||
} else {
|
} else {
|
||||||
s->dev[port].port_regs.sig = SATA_SIGNATURE_DISK;
|
pr->sig = SATA_SIGNATURE_DISK;
|
||||||
ide_state->status = SEEK_STAT | WRERR_STAT;
|
ide_state->status = SEEK_STAT | WRERR_STAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,7 +563,8 @@ static void debug_print_fis(uint8_t *fis, int cmd_len)
|
|||||||
|
|
||||||
static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
|
static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
|
||||||
{
|
{
|
||||||
AHCIPortRegs *pr = &s->dev[port].port_regs;
|
AHCIDevice *ad = &s->dev[port];
|
||||||
|
AHCIPortRegs *pr = &ad->port_regs;
|
||||||
IDEState *ide_state;
|
IDEState *ide_state;
|
||||||
uint8_t *sdb_fis;
|
uint8_t *sdb_fis;
|
||||||
|
|
||||||
@ -572,8 +573,8 @@ static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sdb_fis = &s->dev[port].res_fis[RES_FIS_SDBFIS];
|
sdb_fis = &ad->res_fis[RES_FIS_SDBFIS];
|
||||||
ide_state = &s->dev[port].port.ifs[0];
|
ide_state = &ad->port.ifs[0];
|
||||||
|
|
||||||
/* clear memory */
|
/* clear memory */
|
||||||
*(uint32_t*)sdb_fis = 0;
|
*(uint32_t*)sdb_fis = 0;
|
||||||
@ -582,9 +583,14 @@ static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
|
|||||||
sdb_fis[0] = ide_state->error;
|
sdb_fis[0] = ide_state->error;
|
||||||
sdb_fis[2] = ide_state->status & 0x77;
|
sdb_fis[2] = ide_state->status & 0x77;
|
||||||
s->dev[port].finished |= finished;
|
s->dev[port].finished |= finished;
|
||||||
*(uint32_t*)(sdb_fis + 4) = cpu_to_le32(s->dev[port].finished);
|
*(uint32_t*)(sdb_fis + 4) = cpu_to_le32(ad->finished);
|
||||||
|
|
||||||
ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_SDB_FIS);
|
/* Update shadow registers (except BSY 0x80 and DRQ 0x08) */
|
||||||
|
pr->tfdata = (ad->port.ifs[0].error << 8) |
|
||||||
|
(ad->port.ifs[0].status & 0x77) |
|
||||||
|
(pr->tfdata & 0x88);
|
||||||
|
|
||||||
|
ahci_trigger_irq(s, ad, PORT_IRQ_SDB_FIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len)
|
static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len)
|
||||||
@ -642,6 +648,10 @@ static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len)
|
|||||||
pio_fis[18] = 0;
|
pio_fis[18] = 0;
|
||||||
pio_fis[19] = 0;
|
pio_fis[19] = 0;
|
||||||
|
|
||||||
|
/* Update shadow registers: */
|
||||||
|
pr->tfdata = (ad->port.ifs[0].error << 8) |
|
||||||
|
ad->port.ifs[0].status;
|
||||||
|
|
||||||
if (pio_fis[2] & ERR_STAT) {
|
if (pio_fis[2] & ERR_STAT) {
|
||||||
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
|
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
|
||||||
}
|
}
|
||||||
@ -693,6 +703,10 @@ static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis)
|
|||||||
d2h_fis[i] = 0;
|
d2h_fis[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update shadow registers: */
|
||||||
|
pr->tfdata = (ad->port.ifs[0].error << 8) |
|
||||||
|
ad->port.ifs[0].status;
|
||||||
|
|
||||||
if (d2h_fis[2] & ERR_STAT) {
|
if (d2h_fis[2] & ERR_STAT) {
|
||||||
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
|
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
|
||||||
}
|
}
|
||||||
@ -791,6 +805,9 @@ static void ncq_cb(void *opaque, int ret)
|
|||||||
NCQTransferState *ncq_tfs = (NCQTransferState *)opaque;
|
NCQTransferState *ncq_tfs = (NCQTransferState *)opaque;
|
||||||
IDEState *ide_state = &ncq_tfs->drive->port.ifs[0];
|
IDEState *ide_state = &ncq_tfs->drive->port.ifs[0];
|
||||||
|
|
||||||
|
if (ret == -ECANCELED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
/* Clear bit for this tag in SActive */
|
/* Clear bit for this tag in SActive */
|
||||||
ncq_tfs->drive->port_regs.scr_act &= ~(1 << ncq_tfs->tag);
|
ncq_tfs->drive->port_regs.scr_act &= ~(1 << ncq_tfs->tag);
|
||||||
|
|
||||||
|
@ -136,6 +136,7 @@ void ide_atapi_cmd_ok(IDEState *s)
|
|||||||
s->error = 0;
|
s->error = 0;
|
||||||
s->status = READY_STAT | SEEK_STAT;
|
s->status = READY_STAT | SEEK_STAT;
|
||||||
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
|
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
|
||||||
|
ide_transfer_stop(s);
|
||||||
ide_set_irq(s->bus);
|
ide_set_irq(s->bus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +150,7 @@ void ide_atapi_cmd_error(IDEState *s, int sense_key, int asc)
|
|||||||
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
|
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
|
||||||
s->sense_key = sense_key;
|
s->sense_key = sense_key;
|
||||||
s->asc = asc;
|
s->asc = asc;
|
||||||
|
ide_transfer_stop(s);
|
||||||
ide_set_irq(s->bus);
|
ide_set_irq(s->bus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,9 +178,7 @@ void ide_atapi_cmd_reply_end(IDEState *s)
|
|||||||
#endif
|
#endif
|
||||||
if (s->packet_transfer_size <= 0) {
|
if (s->packet_transfer_size <= 0) {
|
||||||
/* end of transfer */
|
/* end of transfer */
|
||||||
s->status = READY_STAT | SEEK_STAT;
|
ide_atapi_cmd_ok(s);
|
||||||
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
|
|
||||||
ide_transfer_stop(s);
|
|
||||||
ide_set_irq(s->bus);
|
ide_set_irq(s->bus);
|
||||||
#ifdef DEBUG_IDE_ATAPI
|
#ifdef DEBUG_IDE_ATAPI
|
||||||
printf("status=0x%x\n", s->status);
|
printf("status=0x%x\n", s->status);
|
||||||
@ -188,7 +188,6 @@ void ide_atapi_cmd_reply_end(IDEState *s)
|
|||||||
if (s->lba != -1 && s->io_buffer_index >= s->cd_sector_size) {
|
if (s->lba != -1 && s->io_buffer_index >= s->cd_sector_size) {
|
||||||
ret = cd_read_sector(s, s->lba, s->io_buffer, s->cd_sector_size);
|
ret = cd_read_sector(s, s->lba, s->io_buffer, s->cd_sector_size);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ide_transfer_stop(s);
|
|
||||||
ide_atapi_io_error(s, ret);
|
ide_atapi_io_error(s, ret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -372,23 +372,21 @@ static void trim_aio_cancel(BlockDriverAIOCB *acb)
|
|||||||
{
|
{
|
||||||
TrimAIOCB *iocb = container_of(acb, TrimAIOCB, common);
|
TrimAIOCB *iocb = container_of(acb, TrimAIOCB, common);
|
||||||
|
|
||||||
/* Exit the loop in case bdrv_aio_cancel calls ide_issue_trim_cb again. */
|
/* Exit the loop so ide_issue_trim_cb will not continue */
|
||||||
iocb->j = iocb->qiov->niov - 1;
|
iocb->j = iocb->qiov->niov - 1;
|
||||||
iocb->i = (iocb->qiov->iov[iocb->j].iov_len / 8) - 1;
|
iocb->i = (iocb->qiov->iov[iocb->j].iov_len / 8) - 1;
|
||||||
|
|
||||||
/* Tell ide_issue_trim_cb not to trigger the completion, too. */
|
iocb->ret = -ECANCELED;
|
||||||
qemu_bh_delete(iocb->bh);
|
|
||||||
iocb->bh = NULL;
|
|
||||||
|
|
||||||
if (iocb->aiocb) {
|
if (iocb->aiocb) {
|
||||||
bdrv_aio_cancel(iocb->aiocb);
|
bdrv_aio_cancel_async(iocb->aiocb);
|
||||||
|
iocb->aiocb = NULL;
|
||||||
}
|
}
|
||||||
qemu_aio_release(iocb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const AIOCBInfo trim_aiocb_info = {
|
static const AIOCBInfo trim_aiocb_info = {
|
||||||
.aiocb_size = sizeof(TrimAIOCB),
|
.aiocb_size = sizeof(TrimAIOCB),
|
||||||
.cancel = trim_aio_cancel,
|
.cancel_async = trim_aio_cancel,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void ide_trim_bh_cb(void *opaque)
|
static void ide_trim_bh_cb(void *opaque)
|
||||||
@ -399,7 +397,7 @@ static void ide_trim_bh_cb(void *opaque)
|
|||||||
|
|
||||||
qemu_bh_delete(iocb->bh);
|
qemu_bh_delete(iocb->bh);
|
||||||
iocb->bh = NULL;
|
iocb->bh = NULL;
|
||||||
qemu_aio_release(iocb);
|
qemu_aio_unref(iocb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ide_issue_trim_cb(void *opaque, int ret)
|
static void ide_issue_trim_cb(void *opaque, int ret)
|
||||||
@ -568,6 +566,9 @@ static void ide_sector_read_cb(void *opaque, int ret)
|
|||||||
s->pio_aiocb = NULL;
|
s->pio_aiocb = NULL;
|
||||||
s->status &= ~BUSY_STAT;
|
s->status &= ~BUSY_STAT;
|
||||||
|
|
||||||
|
if (ret == -ECANCELED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
block_acct_done(bdrv_get_stats(s->bs), &s->acct);
|
block_acct_done(bdrv_get_stats(s->bs), &s->acct);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO |
|
if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO |
|
||||||
@ -678,6 +679,9 @@ void ide_dma_cb(void *opaque, int ret)
|
|||||||
int64_t sector_num;
|
int64_t sector_num;
|
||||||
bool stay_active = false;
|
bool stay_active = false;
|
||||||
|
|
||||||
|
if (ret == -ECANCELED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
int op = IDE_RETRY_DMA;
|
int op = IDE_RETRY_DMA;
|
||||||
|
|
||||||
@ -803,6 +807,9 @@ static void ide_sector_write_cb(void *opaque, int ret)
|
|||||||
IDEState *s = opaque;
|
IDEState *s = opaque;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
|
if (ret == -ECANCELED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
block_acct_done(bdrv_get_stats(s->bs), &s->acct);
|
block_acct_done(bdrv_get_stats(s->bs), &s->acct);
|
||||||
|
|
||||||
s->pio_aiocb = NULL;
|
s->pio_aiocb = NULL;
|
||||||
@ -882,6 +889,9 @@ static void ide_flush_cb(void *opaque, int ret)
|
|||||||
|
|
||||||
s->pio_aiocb = NULL;
|
s->pio_aiocb = NULL;
|
||||||
|
|
||||||
|
if (ret == -ECANCELED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
/* XXX: What sector number to set here? */
|
/* XXX: What sector number to set here? */
|
||||||
if (ide_handle_rw_error(s, -ret, IDE_RETRY_FLUSH)) {
|
if (ide_handle_rw_error(s, -ret, IDE_RETRY_FLUSH)) {
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
#include <hw/ide/pci.h>
|
#include <hw/ide/pci.h>
|
||||||
#include <hw/ide/ahci.h>
|
#include <hw/ide/ahci.h>
|
||||||
|
|
||||||
|
#define ICH9_MSI_CAP_OFFSET 0x80
|
||||||
#define ICH9_SATA_CAP_OFFSET 0xA8
|
#define ICH9_SATA_CAP_OFFSET 0xA8
|
||||||
|
|
||||||
#define ICH9_IDP_BAR 4
|
#define ICH9_IDP_BAR 4
|
||||||
@ -115,7 +116,6 @@ static int pci_ich9_ahci_init(PCIDevice *dev)
|
|||||||
/* XXX Software should program this register */
|
/* XXX Software should program this register */
|
||||||
dev->config[0x90] = 1 << 6; /* Address Map Register - AHCI mode */
|
dev->config[0x90] = 1 << 6; /* Address Map Register - AHCI mode */
|
||||||
|
|
||||||
msi_init(dev, 0x50, 1, true, false);
|
|
||||||
d->ahci.irq = pci_allocate_irq(dev);
|
d->ahci.irq = pci_allocate_irq(dev);
|
||||||
|
|
||||||
pci_register_bar(dev, ICH9_IDP_BAR, PCI_BASE_ADDRESS_SPACE_IO,
|
pci_register_bar(dev, ICH9_IDP_BAR, PCI_BASE_ADDRESS_SPACE_IO,
|
||||||
@ -135,6 +135,11 @@ static int pci_ich9_ahci_init(PCIDevice *dev)
|
|||||||
(ICH9_IDP_BAR + 0x4) | (ICH9_IDP_INDEX_LOG2 << 4));
|
(ICH9_IDP_BAR + 0x4) | (ICH9_IDP_INDEX_LOG2 << 4));
|
||||||
d->ahci.idp_offset = ICH9_IDP_INDEX;
|
d->ahci.idp_offset = ICH9_IDP_INDEX;
|
||||||
|
|
||||||
|
/* Although the AHCI 1.3 specification states that the first capability
|
||||||
|
* should be PMCAP, the Intel ICH9 data sheet specifies that the ICH9
|
||||||
|
* AHCI device puts the MSI capability first, pointing to 0x80. */
|
||||||
|
msi_init(dev, ICH9_MSI_CAP_OFFSET, 1, true, false);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <linux/kvm.h>
|
#include <linux/kvm.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <linux/virtio_ring.h>
|
|
||||||
#include <netpacket/packet.h>
|
#include <netpacket/packet.h>
|
||||||
#include <net/ethernet.h>
|
#include <net/ethernet.h>
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
@ -36,6 +35,7 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "hw/virtio/virtio_ring.h"
|
||||||
#include "hw/virtio/vhost.h"
|
#include "hw/virtio/vhost.h"
|
||||||
#include "hw/virtio/virtio-bus.h"
|
#include "hw/virtio/virtio-bus.h"
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ common-obj-y += virtio-rng.o
|
|||||||
common-obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o
|
common-obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o
|
||||||
common-obj-y += virtio-bus.o
|
common-obj-y += virtio-bus.o
|
||||||
common-obj-y += virtio-mmio.o
|
common-obj-y += virtio-mmio.o
|
||||||
common-obj-$(CONFIG_VIRTIO_BLK_DATA_PLANE) += dataplane/
|
common-obj-$(CONFIG_VIRTIO) += dataplane/
|
||||||
|
|
||||||
obj-y += virtio.o virtio-balloon.o
|
obj-y += virtio.o virtio-balloon.o
|
||||||
obj-$(CONFIG_LINUX) += vhost.o vhost-backend.o vhost-user.o
|
obj-$(CONFIG_LINUX) += vhost.o vhost-backend.o vhost-user.o
|
||||||
|
@ -181,7 +181,8 @@ static int get_desc(Vring *vring, VirtQueueElement *elem,
|
|||||||
|
|
||||||
/* Stop for now if there are not enough iovecs available. */
|
/* Stop for now if there are not enough iovecs available. */
|
||||||
if (*num >= VIRTQUEUE_MAX_SIZE) {
|
if (*num >= VIRTQUEUE_MAX_SIZE) {
|
||||||
return -ENOBUFS;
|
error_report("Invalid SG num: %u", *num);
|
||||||
|
return -EFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO handle non-contiguous memory across region boundaries */
|
/* TODO handle non-contiguous memory across region boundaries */
|
||||||
|
@ -26,7 +26,8 @@ typedef struct BlockDriverAIOCB BlockDriverAIOCB;
|
|||||||
typedef void BlockDriverCompletionFunc(void *opaque, int ret);
|
typedef void BlockDriverCompletionFunc(void *opaque, int ret);
|
||||||
|
|
||||||
typedef struct AIOCBInfo {
|
typedef struct AIOCBInfo {
|
||||||
void (*cancel)(BlockDriverAIOCB *acb);
|
void (*cancel_async)(BlockDriverAIOCB *acb);
|
||||||
|
AioContext *(*get_aio_context)(BlockDriverAIOCB *acb);
|
||||||
size_t aiocb_size;
|
size_t aiocb_size;
|
||||||
} AIOCBInfo;
|
} AIOCBInfo;
|
||||||
|
|
||||||
@ -35,11 +36,13 @@ struct BlockDriverAIOCB {
|
|||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
BlockDriverCompletionFunc *cb;
|
BlockDriverCompletionFunc *cb;
|
||||||
void *opaque;
|
void *opaque;
|
||||||
|
int refcnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
void *qemu_aio_get(const AIOCBInfo *aiocb_info, BlockDriverState *bs,
|
void *qemu_aio_get(const AIOCBInfo *aiocb_info, BlockDriverState *bs,
|
||||||
BlockDriverCompletionFunc *cb, void *opaque);
|
BlockDriverCompletionFunc *cb, void *opaque);
|
||||||
void qemu_aio_release(void *p);
|
void qemu_aio_unref(void *p);
|
||||||
|
void qemu_aio_ref(void *p);
|
||||||
|
|
||||||
typedef struct AioHandler AioHandler;
|
typedef struct AioHandler AioHandler;
|
||||||
typedef void QEMUBHFunc(void *opaque);
|
typedef void QEMUBHFunc(void *opaque);
|
||||||
@ -99,7 +102,7 @@ void aio_set_dispatching(AioContext *ctx, bool dispatching);
|
|||||||
* They also provide bottom halves, a service to execute a piece of code
|
* They also provide bottom halves, a service to execute a piece of code
|
||||||
* as soon as possible.
|
* as soon as possible.
|
||||||
*/
|
*/
|
||||||
AioContext *aio_context_new(void);
|
AioContext *aio_context_new(Error **errp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aio_context_ref:
|
* aio_context_ref:
|
||||||
|
@ -337,6 +337,7 @@ BlockDriverAIOCB *bdrv_aio_discard(BlockDriverState *bs,
|
|||||||
int64_t sector_num, int nb_sectors,
|
int64_t sector_num, int nb_sectors,
|
||||||
BlockDriverCompletionFunc *cb, void *opaque);
|
BlockDriverCompletionFunc *cb, void *opaque);
|
||||||
void bdrv_aio_cancel(BlockDriverAIOCB *acb);
|
void bdrv_aio_cancel(BlockDriverAIOCB *acb);
|
||||||
|
void bdrv_aio_cancel_async(BlockDriverAIOCB *acb);
|
||||||
|
|
||||||
typedef struct BlockRequest {
|
typedef struct BlockRequest {
|
||||||
/* Fields to be filled by multiwrite caller */
|
/* Fields to be filled by multiwrite caller */
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
#ifndef VRING_H
|
#ifndef VRING_H
|
||||||
#define VRING_H
|
#define VRING_H
|
||||||
|
|
||||||
#include <linux/virtio_ring.h>
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
|
#include "hw/virtio/virtio_ring.h"
|
||||||
#include "hw/virtio/virtio.h"
|
#include "hw/virtio/virtio.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -132,10 +132,8 @@ typedef struct VirtIOBlock {
|
|||||||
VMChangeStateEntry *change;
|
VMChangeStateEntry *change;
|
||||||
/* Function to push to vq and notify guest */
|
/* Function to push to vq and notify guest */
|
||||||
void (*complete_request)(struct VirtIOBlockReq *req, unsigned char status);
|
void (*complete_request)(struct VirtIOBlockReq *req, unsigned char status);
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
Notifier migration_state_notifier;
|
Notifier migration_state_notifier;
|
||||||
struct VirtIOBlockDataPlane *dataplane;
|
struct VirtIOBlockDataPlane *dataplane;
|
||||||
#endif
|
|
||||||
} VirtIOBlock;
|
} VirtIOBlock;
|
||||||
|
|
||||||
typedef struct MultiReqBuffer {
|
typedef struct MultiReqBuffer {
|
||||||
|
167
include/hw/virtio/virtio_ring.h
Normal file
167
include/hw/virtio/virtio_ring.h
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
#ifndef _LINUX_VIRTIO_RING_H
|
||||||
|
#define _LINUX_VIRTIO_RING_H
|
||||||
|
/*
|
||||||
|
* This file is copied from /usr/include/linux while converting __uNN types
|
||||||
|
* to uXX_t, __inline__ to inline, and tab to spaces.
|
||||||
|
* */
|
||||||
|
|
||||||
|
/* An interface for efficient virtio implementation, currently for use by KVM
|
||||||
|
* and lguest, but hopefully others soon. Do NOT change this since it will
|
||||||
|
* break existing servers and clients.
|
||||||
|
*
|
||||||
|
* This header is BSD licensed so anyone can use the definitions to implement
|
||||||
|
* compatible drivers/servers.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of IBM nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL IBM OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* Copyright Rusty Russell IBM Corporation 2007. */
|
||||||
|
|
||||||
|
/* This marks a buffer as continuing via the next field. */
|
||||||
|
#define VRING_DESC_F_NEXT 1
|
||||||
|
/* This marks a buffer as write-only (otherwise read-only). */
|
||||||
|
#define VRING_DESC_F_WRITE 2
|
||||||
|
/* This means the buffer contains a list of buffer descriptors. */
|
||||||
|
#define VRING_DESC_F_INDIRECT 4
|
||||||
|
|
||||||
|
/* The Host uses this in used->flags to advise the Guest: don't kick me when
|
||||||
|
* you add a buffer. It's unreliable, so it's simply an optimization. Guest
|
||||||
|
* will still kick if it's out of buffers. */
|
||||||
|
#define VRING_USED_F_NO_NOTIFY 1
|
||||||
|
/* The Guest uses this in avail->flags to advise the Host: don't interrupt me
|
||||||
|
* when you consume a buffer. It's unreliable, so it's simply an
|
||||||
|
* optimization. */
|
||||||
|
#define VRING_AVAIL_F_NO_INTERRUPT 1
|
||||||
|
|
||||||
|
/* We support indirect buffer descriptors */
|
||||||
|
#define VIRTIO_RING_F_INDIRECT_DESC 28
|
||||||
|
|
||||||
|
/* The Guest publishes the used index for which it expects an interrupt
|
||||||
|
* at the end of the avail ring. Host should ignore the avail->flags field. */
|
||||||
|
/* The Host publishes the avail index for which it expects a kick
|
||||||
|
* at the end of the used ring. Guest should ignore the used->flags field. */
|
||||||
|
#define VIRTIO_RING_F_EVENT_IDX 29
|
||||||
|
|
||||||
|
/* Virtio ring descriptors: 16 bytes. These can chain together via "next". */
|
||||||
|
struct vring_desc {
|
||||||
|
/* Address (guest-physical). */
|
||||||
|
uint64_t addr;
|
||||||
|
/* Length. */
|
||||||
|
uint32_t len;
|
||||||
|
/* The flags as indicated above. */
|
||||||
|
uint16_t flags;
|
||||||
|
/* We chain unused descriptors via this, too */
|
||||||
|
uint16_t next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct vring_avail {
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t idx;
|
||||||
|
uint16_t ring[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* u32 is used here for ids for padding reasons. */
|
||||||
|
struct vring_used_elem {
|
||||||
|
/* Index of start of used descriptor chain. */
|
||||||
|
uint32_t id;
|
||||||
|
/* Total length of the descriptor chain which was used (written to) */
|
||||||
|
uint32_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct vring_used {
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t idx;
|
||||||
|
struct vring_used_elem ring[];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct vring {
|
||||||
|
unsigned int num;
|
||||||
|
|
||||||
|
struct vring_desc *desc;
|
||||||
|
|
||||||
|
struct vring_avail *avail;
|
||||||
|
|
||||||
|
struct vring_used *used;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* The standard layout for the ring is a continuous chunk of memory which looks
|
||||||
|
* like this. We assume num is a power of 2.
|
||||||
|
*
|
||||||
|
* struct vring
|
||||||
|
* {
|
||||||
|
* // The actual descriptors (16 bytes each)
|
||||||
|
* struct vring_desc desc[num];
|
||||||
|
*
|
||||||
|
* // A ring of available descriptor heads with free-running index.
|
||||||
|
* uint16_t avail_flags;
|
||||||
|
* uint16_t avail_idx;
|
||||||
|
* uint16_t available[num];
|
||||||
|
* uint16_t used_event_idx;
|
||||||
|
*
|
||||||
|
* // Padding to the next align boundary.
|
||||||
|
* char pad[];
|
||||||
|
*
|
||||||
|
* // A ring of used descriptor heads with free-running index.
|
||||||
|
* uint16_t used_flags;
|
||||||
|
* uint16_t used_idx;
|
||||||
|
* struct vring_used_elem used[num];
|
||||||
|
* uint16_t avail_event_idx;
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
/* We publish the used event index at the end of the available ring, and vice
|
||||||
|
* versa. They are at the end for backwards compatibility. */
|
||||||
|
#define vring_used_event(vr) ((vr)->avail->ring[(vr)->num])
|
||||||
|
#define vring_avail_event(vr) (*(uint16_t *)&(vr)->used->ring[(vr)->num])
|
||||||
|
|
||||||
|
static inline void vring_init(struct vring *vr, unsigned int num, void *p,
|
||||||
|
unsigned long align)
|
||||||
|
{
|
||||||
|
vr->num = num;
|
||||||
|
vr->desc = p;
|
||||||
|
vr->avail = p + num*sizeof(struct vring_desc);
|
||||||
|
vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + sizeof(uint16_t)
|
||||||
|
+ align-1) & ~(align - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline unsigned vring_size(unsigned int num, unsigned long align)
|
||||||
|
{
|
||||||
|
return ((sizeof(struct vring_desc) * num + sizeof(uint16_t) * (3 + num)
|
||||||
|
+ align - 1) & ~(align - 1))
|
||||||
|
+ sizeof(uint16_t) * 3 + sizeof(struct vring_used_elem) * num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The following is used with USED_EVENT_IDX and AVAIL_EVENT_IDX */
|
||||||
|
/* Assuming a given event_idx value from the other size, if
|
||||||
|
* we have just incremented index from old to new_idx,
|
||||||
|
* should we trigger an event? */
|
||||||
|
static inline int vring_need_event(uint16_t event_idx, uint16_t new_idx, uint16_t old)
|
||||||
|
{
|
||||||
|
/* Note: Xen has similar logic for notification hold-off
|
||||||
|
* in include/xen/interface/io/ring.h with req_event and req_prod
|
||||||
|
* corresponding to event_idx + 1 and new_idx respectively.
|
||||||
|
* Note also that req_event and req_prod in Xen start at 1,
|
||||||
|
* event indexes in virtio start at 0. */
|
||||||
|
return (uint16_t)(new_idx - event_idx - 1) < (uint16_t)(new_idx - old);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _LINUX_VIRTIO_RING_H */
|
@ -42,7 +42,7 @@
|
|||||||
*
|
*
|
||||||
* In the case of QEMU tools, this will also start/initialize timers.
|
* In the case of QEMU tools, this will also start/initialize timers.
|
||||||
*/
|
*/
|
||||||
int qemu_init_main_loop(void);
|
int qemu_init_main_loop(Error **errp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* main_loop_wait: Run one iteration of the main loop.
|
* main_loop_wait: Run one iteration of the main loop.
|
||||||
|
11
iothread.c
11
iothread.c
@ -17,6 +17,7 @@
|
|||||||
#include "block/aio.h"
|
#include "block/aio.h"
|
||||||
#include "sysemu/iothread.h"
|
#include "sysemu/iothread.h"
|
||||||
#include "qmp-commands.h"
|
#include "qmp-commands.h"
|
||||||
|
#include "qemu/error-report.h"
|
||||||
|
|
||||||
#define IOTHREADS_PATH "/objects"
|
#define IOTHREADS_PATH "/objects"
|
||||||
|
|
||||||
@ -53,6 +54,9 @@ static void iothread_instance_finalize(Object *obj)
|
|||||||
{
|
{
|
||||||
IOThread *iothread = IOTHREAD(obj);
|
IOThread *iothread = IOTHREAD(obj);
|
||||||
|
|
||||||
|
if (!iothread->ctx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
iothread->stopping = true;
|
iothread->stopping = true;
|
||||||
aio_notify(iothread->ctx);
|
aio_notify(iothread->ctx);
|
||||||
qemu_thread_join(&iothread->thread);
|
qemu_thread_join(&iothread->thread);
|
||||||
@ -63,11 +67,16 @@ static void iothread_instance_finalize(Object *obj)
|
|||||||
|
|
||||||
static void iothread_complete(UserCreatable *obj, Error **errp)
|
static void iothread_complete(UserCreatable *obj, Error **errp)
|
||||||
{
|
{
|
||||||
|
Error *local_error = NULL;
|
||||||
IOThread *iothread = IOTHREAD(obj);
|
IOThread *iothread = IOTHREAD(obj);
|
||||||
|
|
||||||
iothread->stopping = false;
|
iothread->stopping = false;
|
||||||
iothread->ctx = aio_context_new();
|
|
||||||
iothread->thread_id = -1;
|
iothread->thread_id = -1;
|
||||||
|
iothread->ctx = aio_context_new(&local_error);
|
||||||
|
if (!iothread->ctx) {
|
||||||
|
error_propagate(errp, local_error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
qemu_mutex_init(&iothread->init_done_lock);
|
qemu_mutex_init(&iothread->init_done_lock);
|
||||||
qemu_cond_init(&iothread->init_done_cond);
|
qemu_cond_init(&iothread->init_done_cond);
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#include <linux/ioctl.h>
|
#include <linux/ioctl.h>
|
||||||
#include <linux/virtio_config.h>
|
#include <linux/virtio_config.h>
|
||||||
#include <linux/virtio_ring.h>
|
#include "hw/virtio/virtio_ring.h"
|
||||||
|
|
||||||
struct vhost_vring_state {
|
struct vhost_vring_state {
|
||||||
unsigned int index;
|
unsigned int index;
|
||||||
|
@ -126,10 +126,11 @@ void qemu_notify_event(void)
|
|||||||
|
|
||||||
static GArray *gpollfds;
|
static GArray *gpollfds;
|
||||||
|
|
||||||
int qemu_init_main_loop(void)
|
int qemu_init_main_loop(Error **errp)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
GSource *src;
|
GSource *src;
|
||||||
|
Error *local_error = NULL;
|
||||||
|
|
||||||
init_clocks();
|
init_clocks();
|
||||||
|
|
||||||
@ -138,8 +139,12 @@ int qemu_init_main_loop(void)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qemu_aio_context = aio_context_new(&local_error);
|
||||||
|
if (!qemu_aio_context) {
|
||||||
|
error_propagate(errp, local_error);
|
||||||
|
return -EMFILE;
|
||||||
|
}
|
||||||
gpollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
|
gpollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
|
||||||
qemu_aio_context = aio_context_new();
|
|
||||||
src = aio_get_g_source(qemu_aio_context);
|
src = aio_get_g_source(qemu_aio_context);
|
||||||
g_source_attach(src, NULL);
|
g_source_attach(src, NULL);
|
||||||
g_source_unref(src);
|
g_source_unref(src);
|
||||||
|
@ -197,7 +197,7 @@
|
|||||||
# 'file', 'file', 'ftp', 'ftps', 'host_cdrom', 'host_device',
|
# 'file', 'file', 'ftp', 'ftps', 'host_cdrom', 'host_device',
|
||||||
# 'host_floppy', 'http', 'https', 'nbd', 'parallels', 'qcow',
|
# 'host_floppy', 'http', 'https', 'nbd', 'parallels', 'qcow',
|
||||||
# 'qcow2', 'raw', 'tftp', 'vdi', 'vmdk', 'vpc', 'vvfat'
|
# 'qcow2', 'raw', 'tftp', 'vdi', 'vmdk', 'vpc', 'vvfat'
|
||||||
# 2.2: 'archipelago'
|
# 2.2: 'archipelago' added, 'cow' dropped
|
||||||
#
|
#
|
||||||
# @backing_file: #optional the name of the backing file (for copy-on-write)
|
# @backing_file: #optional the name of the backing file (for copy-on-write)
|
||||||
#
|
#
|
||||||
@ -1148,10 +1148,11 @@
|
|||||||
# Since: 2.0
|
# Since: 2.0
|
||||||
##
|
##
|
||||||
{ 'enum': 'BlockdevDriver',
|
{ 'enum': 'BlockdevDriver',
|
||||||
'data': [ 'archipelago', 'file', 'host_device', 'host_cdrom', 'host_floppy',
|
'data': [ 'archipelago', 'blkdebug', 'blkverify', 'bochs', 'cloop',
|
||||||
'http', 'https', 'ftp', 'ftps', 'tftp', 'vvfat', 'blkdebug',
|
'dmg', 'file', 'ftp', 'ftps', 'host_cdrom', 'host_device',
|
||||||
'blkverify', 'bochs', 'cloop', 'cow', 'dmg', 'parallels', 'qcow',
|
'host_floppy', 'http', 'https', 'null-aio', 'null-co', 'parallels',
|
||||||
'qcow2', 'qed', 'raw', 'vdi', 'vhdx', 'vmdk', 'vpc', 'quorum' ] }
|
'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'tftp', 'vdi', 'vhdx',
|
||||||
|
'vmdk', 'vpc', 'vvfat' ] }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockdevOptionsBase
|
# @BlockdevOptionsBase
|
||||||
@ -1203,6 +1204,18 @@
|
|||||||
{ 'type': 'BlockdevOptionsFile',
|
{ 'type': 'BlockdevOptionsFile',
|
||||||
'data': { 'filename': 'str' } }
|
'data': { 'filename': 'str' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @BlockdevOptionsNull
|
||||||
|
#
|
||||||
|
# Driver specific block device options for the null backend.
|
||||||
|
#
|
||||||
|
# @size: #optional size of the device in bytes.
|
||||||
|
#
|
||||||
|
# Since: 2.2
|
||||||
|
##
|
||||||
|
{ 'type': 'BlockdevOptionsNull',
|
||||||
|
'data': { '*size': 'int' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockdevOptionsVVFAT
|
# @BlockdevOptionsVVFAT
|
||||||
#
|
#
|
||||||
@ -1250,6 +1263,67 @@
|
|||||||
'base': 'BlockdevOptionsGenericFormat',
|
'base': 'BlockdevOptionsGenericFormat',
|
||||||
'data': { '*backing': 'BlockdevRef' } }
|
'data': { '*backing': 'BlockdevRef' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Qcow2OverlapCheckMode
|
||||||
|
#
|
||||||
|
# General overlap check modes.
|
||||||
|
#
|
||||||
|
# @none: Do not perform any checks
|
||||||
|
#
|
||||||
|
# @constant: Perform only checks which can be done in constant time and
|
||||||
|
# without reading anything from disk
|
||||||
|
#
|
||||||
|
# @cached: Perform only checks which can be done without reading anything
|
||||||
|
# from disk
|
||||||
|
#
|
||||||
|
# @all: Perform all available overlap checks
|
||||||
|
#
|
||||||
|
# Since: 2.2
|
||||||
|
##
|
||||||
|
{ 'enum': 'Qcow2OverlapCheckMode',
|
||||||
|
'data': [ 'none', 'constant', 'cached', 'all' ] }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Qcow2OverlapCheckFlags
|
||||||
|
#
|
||||||
|
# Structure of flags for each metadata structure. Setting a field to 'true'
|
||||||
|
# makes qemu guard that structure against unintended overwriting. The default
|
||||||
|
# value is chosen according to the template given.
|
||||||
|
#
|
||||||
|
# @template: Specifies a template mode which can be adjusted using the other
|
||||||
|
# flags, defaults to 'cached'
|
||||||
|
#
|
||||||
|
# Since: 2.2
|
||||||
|
##
|
||||||
|
{ 'type': 'Qcow2OverlapCheckFlags',
|
||||||
|
'data': { '*template': 'Qcow2OverlapCheckMode',
|
||||||
|
'*main-header': 'bool',
|
||||||
|
'*active-l1': 'bool',
|
||||||
|
'*active-l2': 'bool',
|
||||||
|
'*refcount-table': 'bool',
|
||||||
|
'*refcount-block': 'bool',
|
||||||
|
'*snapshot-table': 'bool',
|
||||||
|
'*inactive-l1': 'bool',
|
||||||
|
'*inactive-l2': 'bool' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Qcow2OverlapChecks
|
||||||
|
#
|
||||||
|
# Specifies which metadata structures should be guarded against unintended
|
||||||
|
# overwriting.
|
||||||
|
#
|
||||||
|
# @flags: set of flags for separate specification of each metadata structure
|
||||||
|
# type
|
||||||
|
#
|
||||||
|
# @mode: named mode which chooses a specific set of flags
|
||||||
|
#
|
||||||
|
# Since: 2.2
|
||||||
|
##
|
||||||
|
{ 'union': 'Qcow2OverlapChecks',
|
||||||
|
'discriminator': {},
|
||||||
|
'data': { 'flags': 'Qcow2OverlapCheckFlags',
|
||||||
|
'mode': 'Qcow2OverlapCheckMode' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockdevOptionsQcow2
|
# @BlockdevOptionsQcow2
|
||||||
#
|
#
|
||||||
@ -1269,6 +1343,18 @@
|
|||||||
# should be issued on other occasions where a cluster
|
# should be issued on other occasions where a cluster
|
||||||
# gets freed
|
# gets freed
|
||||||
#
|
#
|
||||||
|
# @overlap-check: #optional which overlap checks to perform for writes
|
||||||
|
# to the image, defaults to 'cached' (since 2.2)
|
||||||
|
#
|
||||||
|
# @cache-size: #optional the maximum total size of the L2 table and
|
||||||
|
# refcount block caches in bytes (since 2.2)
|
||||||
|
#
|
||||||
|
# @l2-cache-size: #optional the maximum size of the L2 table cache in
|
||||||
|
# bytes (since 2.2)
|
||||||
|
#
|
||||||
|
# @refcount-cache-size: #optional the maximum size of the refcount block cache
|
||||||
|
# in bytes (since 2.2)
|
||||||
|
#
|
||||||
# Since: 1.7
|
# Since: 1.7
|
||||||
##
|
##
|
||||||
{ 'type': 'BlockdevOptionsQcow2',
|
{ 'type': 'BlockdevOptionsQcow2',
|
||||||
@ -1276,7 +1362,11 @@
|
|||||||
'data': { '*lazy-refcounts': 'bool',
|
'data': { '*lazy-refcounts': 'bool',
|
||||||
'*pass-discard-request': 'bool',
|
'*pass-discard-request': 'bool',
|
||||||
'*pass-discard-snapshot': 'bool',
|
'*pass-discard-snapshot': 'bool',
|
||||||
'*pass-discard-other': 'bool' } }
|
'*pass-discard-other': 'bool',
|
||||||
|
'*overlap-check': 'Qcow2OverlapChecks',
|
||||||
|
'*cache-size': 'int',
|
||||||
|
'*l2-cache-size': 'int',
|
||||||
|
'*refcount-cache-size': 'int' } }
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
@ -1471,39 +1561,40 @@
|
|||||||
'discriminator': 'driver',
|
'discriminator': 'driver',
|
||||||
'data': {
|
'data': {
|
||||||
'archipelago':'BlockdevOptionsArchipelago',
|
'archipelago':'BlockdevOptionsArchipelago',
|
||||||
'file': 'BlockdevOptionsFile',
|
|
||||||
'host_device':'BlockdevOptionsFile',
|
|
||||||
'host_cdrom': 'BlockdevOptionsFile',
|
|
||||||
'host_floppy':'BlockdevOptionsFile',
|
|
||||||
'http': 'BlockdevOptionsFile',
|
|
||||||
'https': 'BlockdevOptionsFile',
|
|
||||||
'ftp': 'BlockdevOptionsFile',
|
|
||||||
'ftps': 'BlockdevOptionsFile',
|
|
||||||
'tftp': 'BlockdevOptionsFile',
|
|
||||||
# TODO gluster: Wait for structured options
|
|
||||||
# TODO iscsi: Wait for structured options
|
|
||||||
# TODO nbd: Should take InetSocketAddress for 'host'?
|
|
||||||
# TODO nfs: Wait for structured options
|
|
||||||
# TODO rbd: Wait for structured options
|
|
||||||
# TODO sheepdog: Wait for structured options
|
|
||||||
# TODO ssh: Should take InetSocketAddress for 'host'?
|
|
||||||
'vvfat': 'BlockdevOptionsVVFAT',
|
|
||||||
'blkdebug': 'BlockdevOptionsBlkdebug',
|
'blkdebug': 'BlockdevOptionsBlkdebug',
|
||||||
'blkverify': 'BlockdevOptionsBlkverify',
|
'blkverify': 'BlockdevOptionsBlkverify',
|
||||||
'bochs': 'BlockdevOptionsGenericFormat',
|
'bochs': 'BlockdevOptionsGenericFormat',
|
||||||
'cloop': 'BlockdevOptionsGenericFormat',
|
'cloop': 'BlockdevOptionsGenericFormat',
|
||||||
'cow': 'BlockdevOptionsGenericCOWFormat',
|
|
||||||
'dmg': 'BlockdevOptionsGenericFormat',
|
'dmg': 'BlockdevOptionsGenericFormat',
|
||||||
|
'file': 'BlockdevOptionsFile',
|
||||||
|
'ftp': 'BlockdevOptionsFile',
|
||||||
|
'ftps': 'BlockdevOptionsFile',
|
||||||
|
# TODO gluster: Wait for structured options
|
||||||
|
'host_cdrom': 'BlockdevOptionsFile',
|
||||||
|
'host_device':'BlockdevOptionsFile',
|
||||||
|
'host_floppy':'BlockdevOptionsFile',
|
||||||
|
'http': 'BlockdevOptionsFile',
|
||||||
|
'https': 'BlockdevOptionsFile',
|
||||||
|
# TODO iscsi: Wait for structured options
|
||||||
|
# TODO nbd: Should take InetSocketAddress for 'host'?
|
||||||
|
# TODO nfs: Wait for structured options
|
||||||
|
'null-aio': 'BlockdevOptionsNull',
|
||||||
|
'null-co': 'BlockdevOptionsNull',
|
||||||
'parallels': 'BlockdevOptionsGenericFormat',
|
'parallels': 'BlockdevOptionsGenericFormat',
|
||||||
'qcow': 'BlockdevOptionsGenericCOWFormat',
|
|
||||||
'qcow2': 'BlockdevOptionsQcow2',
|
'qcow2': 'BlockdevOptionsQcow2',
|
||||||
|
'qcow': 'BlockdevOptionsGenericCOWFormat',
|
||||||
'qed': 'BlockdevOptionsGenericCOWFormat',
|
'qed': 'BlockdevOptionsGenericCOWFormat',
|
||||||
|
'quorum': 'BlockdevOptionsQuorum',
|
||||||
'raw': 'BlockdevOptionsGenericFormat',
|
'raw': 'BlockdevOptionsGenericFormat',
|
||||||
|
# TODO rbd: Wait for structured options
|
||||||
|
# TODO sheepdog: Wait for structured options
|
||||||
|
# TODO ssh: Should take InetSocketAddress for 'host'?
|
||||||
|
'tftp': 'BlockdevOptionsFile',
|
||||||
'vdi': 'BlockdevOptionsGenericFormat',
|
'vdi': 'BlockdevOptionsGenericFormat',
|
||||||
'vhdx': 'BlockdevOptionsGenericFormat',
|
'vhdx': 'BlockdevOptionsGenericFormat',
|
||||||
'vmdk': 'BlockdevOptionsGenericCOWFormat',
|
'vmdk': 'BlockdevOptionsGenericCOWFormat',
|
||||||
'vpc': 'BlockdevOptionsGenericFormat',
|
'vpc': 'BlockdevOptionsGenericFormat',
|
||||||
'quorum': 'BlockdevOptionsQuorum'
|
'vvfat': 'BlockdevOptionsVVFAT'
|
||||||
} }
|
} }
|
||||||
|
|
||||||
##
|
##
|
||||||
@ -1555,7 +1646,7 @@
|
|||||||
##
|
##
|
||||||
# @BLOCK_IMAGE_CORRUPTED
|
# @BLOCK_IMAGE_CORRUPTED
|
||||||
#
|
#
|
||||||
# Emitted when a disk image is being marked corrupt
|
# Emitted when a corruption has been detected in a disk image
|
||||||
#
|
#
|
||||||
# @device: device name
|
# @device: device name
|
||||||
#
|
#
|
||||||
@ -1569,13 +1660,18 @@
|
|||||||
# @size: #optional, if the corruption resulted from an image access, this is
|
# @size: #optional, if the corruption resulted from an image access, this is
|
||||||
# the access size
|
# the access size
|
||||||
#
|
#
|
||||||
|
# fatal: if set, the image is marked corrupt and therefore unusable after this
|
||||||
|
# event and must be repaired (Since 2.2; before, every
|
||||||
|
# BLOCK_IMAGE_CORRUPTED event was fatal)
|
||||||
|
#
|
||||||
# Since: 1.7
|
# Since: 1.7
|
||||||
##
|
##
|
||||||
{ 'event': 'BLOCK_IMAGE_CORRUPTED',
|
{ 'event': 'BLOCK_IMAGE_CORRUPTED',
|
||||||
'data': { 'device' : 'str',
|
'data': { 'device' : 'str',
|
||||||
'msg' : 'str',
|
'msg' : 'str',
|
||||||
'*offset': 'int',
|
'*offset': 'int',
|
||||||
'*size' : 'int' } }
|
'*size' : 'int',
|
||||||
|
'fatal' : 'bool' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BLOCK_IO_ERROR
|
# @BLOCK_IO_ERROR
|
||||||
|
@ -206,7 +206,7 @@ int qdev_device_help(QemuOpts *opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
prop_list = qmp_device_list_properties(driver, &local_err);
|
prop_list = qmp_device_list_properties(driver, &local_err);
|
||||||
if (!prop_list) {
|
if (local_err) {
|
||||||
error_printf("%s\n", error_get_pretty(local_err));
|
error_printf("%s\n", error_get_pretty(local_err));
|
||||||
error_free(local_err);
|
error_free(local_err);
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -654,15 +654,6 @@ File name of a base image (see @option{create} subcommand)
|
|||||||
If this option is set to @code{on}, the image is encrypted.
|
If this option is set to @code{on}, the image is encrypted.
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
@item cow
|
|
||||||
User Mode Linux Copy On Write image format. It is supported only for
|
|
||||||
compatibility with previous versions.
|
|
||||||
Supported options:
|
|
||||||
@table @code
|
|
||||||
@item backing_file
|
|
||||||
File name of a base image (see @option{create} subcommand)
|
|
||||||
@end table
|
|
||||||
|
|
||||||
@item vdi
|
@item vdi
|
||||||
VirtualBox 1.1 compatible image format.
|
VirtualBox 1.1 compatible image format.
|
||||||
Supported options:
|
Supported options:
|
||||||
|
@ -2879,6 +2879,7 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
const img_cmd_t *cmd;
|
const img_cmd_t *cmd;
|
||||||
const char *cmdname;
|
const char *cmdname;
|
||||||
|
Error *local_error = NULL;
|
||||||
int c;
|
int c;
|
||||||
static const struct option long_options[] = {
|
static const struct option long_options[] = {
|
||||||
{"help", no_argument, 0, 'h'},
|
{"help", no_argument, 0, 'h'},
|
||||||
@ -2893,7 +2894,12 @@ int main(int argc, char **argv)
|
|||||||
error_set_progname(argv[0]);
|
error_set_progname(argv[0]);
|
||||||
qemu_init_exec_dir(argv[0]);
|
qemu_init_exec_dir(argv[0]);
|
||||||
|
|
||||||
qemu_init_main_loop();
|
if (qemu_init_main_loop(&local_error)) {
|
||||||
|
error_report("%s", error_get_pretty(local_error));
|
||||||
|
error_free(local_error);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
bdrv_init();
|
bdrv_init();
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
error_exit("Not enough arguments");
|
error_exit("Not enough arguments");
|
||||||
|
@ -228,8 +228,8 @@ compression is read-only. It means that if a compressed sector is
|
|||||||
rewritten, then it is rewritten as uncompressed data.
|
rewritten, then it is rewritten as uncompressed data.
|
||||||
|
|
||||||
Image conversion is also useful to get smaller image when using a
|
Image conversion is also useful to get smaller image when using a
|
||||||
growable format such as @code{qcow} or @code{cow}: the empty sectors
|
growable format such as @code{qcow}: the empty sectors are detected and
|
||||||
are detected and suppressed from the destination image.
|
suppressed from the destination image.
|
||||||
|
|
||||||
@var{sparse_size} indicates the consecutive number of bytes (defaults to 4k)
|
@var{sparse_size} indicates the consecutive number of bytes (defaults to 4k)
|
||||||
that must contain only zeros for qemu-img to create a sparse image during
|
that must contain only zeros for qemu-img to create a sparse image during
|
||||||
|
@ -379,6 +379,7 @@ int main(int argc, char **argv)
|
|||||||
int c;
|
int c;
|
||||||
int opt_index = 0;
|
int opt_index = 0;
|
||||||
int flags = BDRV_O_UNMAP;
|
int flags = BDRV_O_UNMAP;
|
||||||
|
Error *local_error = NULL;
|
||||||
|
|
||||||
#ifdef CONFIG_POSIX
|
#ifdef CONFIG_POSIX
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
@ -444,7 +445,11 @@ int main(int argc, char **argv)
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_init_main_loop();
|
if (qemu_init_main_loop(&local_error)) {
|
||||||
|
error_report("%s", error_get_pretty(local_error));
|
||||||
|
error_free(local_error);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
bdrv_init();
|
bdrv_init();
|
||||||
|
|
||||||
/* initialize commands */
|
/* initialize commands */
|
||||||
|
@ -674,7 +674,11 @@ int main(int argc, char **argv)
|
|||||||
snprintf(sockpath, 128, SOCKET_PATH, basename(device));
|
snprintf(sockpath, 128, SOCKET_PATH, basename(device));
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_init_main_loop();
|
if (qemu_init_main_loop(&local_err)) {
|
||||||
|
error_report("%s", error_get_pretty(local_err));
|
||||||
|
error_free(local_err);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
bdrv_init();
|
bdrv_init();
|
||||||
atexit(bdrv_close_all);
|
atexit(bdrv_close_all);
|
||||||
|
|
||||||
|
@ -2081,7 +2081,7 @@ Each json-object contain the following:
|
|||||||
- "file": device file name (json-string)
|
- "file": device file name (json-string)
|
||||||
- "ro": true if read-only, false otherwise (json-bool)
|
- "ro": true if read-only, false otherwise (json-bool)
|
||||||
- "drv": driver format name (json-string)
|
- "drv": driver format name (json-string)
|
||||||
- Possible values: "blkdebug", "bochs", "cloop", "cow", "dmg",
|
- Possible values: "blkdebug", "bochs", "cloop", "dmg",
|
||||||
"file", "file", "ftp", "ftps", "host_cdrom",
|
"file", "file", "ftp", "ftps", "host_cdrom",
|
||||||
"host_device", "host_floppy", "http", "https",
|
"host_device", "host_floppy", "http", "https",
|
||||||
"nbd", "parallels", "qcow", "qcow2", "raw",
|
"nbd", "parallels", "qcow", "qcow2", "raw",
|
||||||
|
@ -177,6 +177,8 @@ const int %(name)s_qtypes[QTYPE_MAX] = {
|
|||||||
qtype = "QTYPE_QDICT"
|
qtype = "QTYPE_QDICT"
|
||||||
elif find_union(qapi_type):
|
elif find_union(qapi_type):
|
||||||
qtype = "QTYPE_QDICT"
|
qtype = "QTYPE_QDICT"
|
||||||
|
elif find_enum(qapi_type):
|
||||||
|
qtype = "QTYPE_QSTRING"
|
||||||
else:
|
else:
|
||||||
assert False, "Invalid anonymous union member"
|
assert False, "Invalid anonymous union member"
|
||||||
|
|
||||||
|
@ -263,7 +263,8 @@ void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **e
|
|||||||
for key in members:
|
for key in members:
|
||||||
assert (members[key] in builtin_types
|
assert (members[key] in builtin_types
|
||||||
or find_struct(members[key])
|
or find_struct(members[key])
|
||||||
or find_union(members[key])), "Invalid anonymous union member"
|
or find_union(members[key])
|
||||||
|
or find_enum(members[key])), "Invalid anonymous union member"
|
||||||
|
|
||||||
enum_full_value = generate_enum_full_value(disc_type, key)
|
enum_full_value = generate_enum_full_value(disc_type, key)
|
||||||
ret += mcgen('''
|
ret += mcgen('''
|
||||||
|
@ -132,6 +132,7 @@ check-qtest-i386-y = tests/endianness-test$(EXESUF)
|
|||||||
check-qtest-i386-y += tests/fdc-test$(EXESUF)
|
check-qtest-i386-y += tests/fdc-test$(EXESUF)
|
||||||
gcov-files-i386-y = hw/block/fdc.c
|
gcov-files-i386-y = hw/block/fdc.c
|
||||||
check-qtest-i386-y += tests/ide-test$(EXESUF)
|
check-qtest-i386-y += tests/ide-test$(EXESUF)
|
||||||
|
check-qtest-i386-y += tests/ahci-test$(EXESUF)
|
||||||
check-qtest-i386-y += tests/hd-geo-test$(EXESUF)
|
check-qtest-i386-y += tests/hd-geo-test$(EXESUF)
|
||||||
gcov-files-i386-y += hw/block/hd-geometry.c
|
gcov-files-i386-y += hw/block/hd-geometry.c
|
||||||
check-qtest-i386-y += tests/boot-order-test$(EXESUF)
|
check-qtest-i386-y += tests/boot-order-test$(EXESUF)
|
||||||
@ -307,6 +308,7 @@ tests/endianness-test$(EXESUF): tests/endianness-test.o
|
|||||||
tests/spapr-phb-test$(EXESUF): tests/spapr-phb-test.o $(libqos-obj-y)
|
tests/spapr-phb-test$(EXESUF): tests/spapr-phb-test.o $(libqos-obj-y)
|
||||||
tests/fdc-test$(EXESUF): tests/fdc-test.o
|
tests/fdc-test$(EXESUF): tests/fdc-test.o
|
||||||
tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y)
|
tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y)
|
||||||
|
tests/ahci-test$(EXESUF): tests/ahci-test.o $(libqos-pc-obj-y)
|
||||||
tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
|
tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
|
||||||
tests/boot-order-test$(EXESUF): tests/boot-order-test.o $(libqos-obj-y)
|
tests/boot-order-test$(EXESUF): tests/boot-order-test.o $(libqos-obj-y)
|
||||||
tests/bios-tables-test$(EXESUF): tests/bios-tables-test.o $(libqos-obj-y)
|
tests/bios-tables-test$(EXESUF): tests/bios-tables-test.o $(libqos-obj-y)
|
||||||
|
1561
tests/ahci-test.c
Normal file
1561
tests/ahci-test.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
UINT8 = 0xff
|
UINT8 = 0xff
|
||||||
|
UINT16 = 0xffff
|
||||||
UINT32 = 0xffffffff
|
UINT32 = 0xffffffff
|
||||||
UINT64 = 0xffffffffffffffff
|
UINT64 = 0xffffffffffffffff
|
||||||
# Most significant bit orders
|
# Most significant bit orders
|
||||||
@ -28,6 +28,8 @@ UINT64_M = 63
|
|||||||
# Fuzz vectors
|
# Fuzz vectors
|
||||||
UINT8_V = [0, 0x10, UINT8/4, UINT8/2 - 1, UINT8/2, UINT8/2 + 1, UINT8 - 1,
|
UINT8_V = [0, 0x10, UINT8/4, UINT8/2 - 1, UINT8/2, UINT8/2 + 1, UINT8 - 1,
|
||||||
UINT8]
|
UINT8]
|
||||||
|
UINT16_V = [0, 0x100, 0x1000, UINT16/4, UINT16/2 - 1, UINT16/2, UINT16/2 + 1,
|
||||||
|
UINT16 - 1, UINT16]
|
||||||
UINT32_V = [0, 0x100, 0x1000, 0x10000, 0x100000, UINT32/4, UINT32/2 - 1,
|
UINT32_V = [0, 0x100, 0x1000, 0x10000, 0x100000, UINT32/4, UINT32/2 - 1,
|
||||||
UINT32/2, UINT32/2 + 1, UINT32 - 1, UINT32]
|
UINT32/2, UINT32/2 + 1, UINT32 - 1, UINT32]
|
||||||
UINT64_V = UINT32_V + [0x1000000, 0x10000000, 0x100000000, UINT64/4,
|
UINT64_V = UINT32_V + [0x1000000, 0x10000000, 0x100000000, UINT64/4,
|
||||||
@ -332,9 +334,8 @@ def l1_entry(current):
|
|||||||
constraints = UINT64_V
|
constraints = UINT64_V
|
||||||
# Reserved bits are ignored
|
# Reserved bits are ignored
|
||||||
# Added a possibility when only flags are fuzzed
|
# Added a possibility when only flags are fuzzed
|
||||||
offset = 0x7fffffffffffffff & random.choice([selector(current,
|
offset = 0x7fffffffffffffff & \
|
||||||
constraints),
|
random.choice([selector(current, constraints), current])
|
||||||
current])
|
|
||||||
is_cow = random.randint(0, 1)
|
is_cow = random.randint(0, 1)
|
||||||
return offset + (is_cow << UINT64_M)
|
return offset + (is_cow << UINT64_M)
|
||||||
|
|
||||||
@ -344,12 +345,23 @@ def l2_entry(current):
|
|||||||
constraints = UINT64_V
|
constraints = UINT64_V
|
||||||
# Reserved bits are ignored
|
# Reserved bits are ignored
|
||||||
# Add a possibility when only flags are fuzzed
|
# Add a possibility when only flags are fuzzed
|
||||||
offset = 0x3ffffffffffffffe & random.choice([selector(current,
|
offset = 0x3ffffffffffffffe & \
|
||||||
constraints),
|
random.choice([selector(current, constraints), current])
|
||||||
current])
|
|
||||||
is_compressed = random.randint(0, 1)
|
is_compressed = random.randint(0, 1)
|
||||||
is_cow = random.randint(0, 1)
|
is_cow = random.randint(0, 1)
|
||||||
is_zero = random.randint(0, 1)
|
is_zero = random.randint(0, 1)
|
||||||
value = offset + (is_cow << UINT64_M) + \
|
value = offset + (is_cow << UINT64_M) + \
|
||||||
(is_compressed << UINT64_M - 1) + is_zero
|
(is_compressed << UINT64_M - 1) + is_zero
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def refcount_table_entry(current):
|
||||||
|
"""Fuzz an entry of the refcount table."""
|
||||||
|
constraints = UINT64_V
|
||||||
|
return selector(current, constraints)
|
||||||
|
|
||||||
|
|
||||||
|
def refcount_block_entry(current):
|
||||||
|
"""Fuzz an entry of a refcount block."""
|
||||||
|
constraints = UINT16_V
|
||||||
|
return selector(current, constraints)
|
||||||
|
@ -102,6 +102,8 @@ class Image(object):
|
|||||||
self.end_of_extension_area = FieldsList()
|
self.end_of_extension_area = FieldsList()
|
||||||
self.l2_tables = FieldsList()
|
self.l2_tables = FieldsList()
|
||||||
self.l1_table = FieldsList()
|
self.l1_table = FieldsList()
|
||||||
|
self.refcount_table = FieldsList()
|
||||||
|
self.refcount_blocks = FieldsList()
|
||||||
self.ext_offset = 0
|
self.ext_offset = 0
|
||||||
self.create_header(cluster_bits, backing_file_name)
|
self.create_header(cluster_bits, backing_file_name)
|
||||||
self.set_backing_file_name(backing_file_name)
|
self.set_backing_file_name(backing_file_name)
|
||||||
@ -113,7 +115,8 @@ class Image(object):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return chain(self.header, self.backing_file_format,
|
return chain(self.header, self.backing_file_format,
|
||||||
self.feature_name_table, self.end_of_extension_area,
|
self.feature_name_table, self.end_of_extension_area,
|
||||||
self.backing_file_name, self.l1_table, self.l2_tables)
|
self.backing_file_name, self.l1_table, self.l2_tables,
|
||||||
|
self.refcount_table, self.refcount_blocks)
|
||||||
|
|
||||||
def create_header(self, cluster_bits, backing_file_name=None):
|
def create_header(self, cluster_bits, backing_file_name=None):
|
||||||
"""Generate a random valid header."""
|
"""Generate a random valid header."""
|
||||||
@ -330,6 +333,138 @@ class Image(object):
|
|||||||
float(self.cluster_size**2)))
|
float(self.cluster_size**2)))
|
||||||
self.header['l1_table_offset'][0].value = l1_offset
|
self.header['l1_table_offset'][0].value = l1_offset
|
||||||
|
|
||||||
|
def create_refcount_structures(self):
|
||||||
|
"""Generate random refcount blocks and refcount table."""
|
||||||
|
def allocate_rfc_blocks(data, size):
|
||||||
|
"""Return indices of clusters allocated for refcount blocks."""
|
||||||
|
cluster_ids = set()
|
||||||
|
diff = block_ids = set([x / size for x in data])
|
||||||
|
while len(diff) != 0:
|
||||||
|
# Allocate all yet not allocated clusters
|
||||||
|
new = self._get_available_clusters(data | cluster_ids,
|
||||||
|
len(diff))
|
||||||
|
# Indices of new refcount blocks necessary to cover clusters
|
||||||
|
# in 'new'
|
||||||
|
diff = set([x / size for x in new]) - block_ids
|
||||||
|
cluster_ids |= new
|
||||||
|
block_ids |= diff
|
||||||
|
return cluster_ids, block_ids
|
||||||
|
|
||||||
|
def allocate_rfc_table(data, init_blocks, block_size):
|
||||||
|
"""Return indices of clusters allocated for the refcount table
|
||||||
|
and updated indices of clusters allocated for blocks and indices
|
||||||
|
of blocks.
|
||||||
|
"""
|
||||||
|
blocks = set(init_blocks)
|
||||||
|
clusters = set()
|
||||||
|
# Number of entries in one cluster of the refcount table
|
||||||
|
size = self.cluster_size / UINT64_S
|
||||||
|
# Number of clusters necessary for the refcount table based on
|
||||||
|
# the current number of refcount blocks
|
||||||
|
table_size = int(ceil((max(blocks) + 1) / float(size)))
|
||||||
|
# Index of the first cluster of the refcount table
|
||||||
|
table_start = self._get_adjacent_clusters(data, table_size + 1)
|
||||||
|
# Clusters allocated for the current length of the refcount table
|
||||||
|
table_clusters = set(range(table_start, table_start + table_size))
|
||||||
|
# Clusters allocated for the refcount table including
|
||||||
|
# last optional one for potential l1 growth
|
||||||
|
table_clusters_allocated = set(range(table_start, table_start +
|
||||||
|
table_size + 1))
|
||||||
|
# New refcount blocks necessary for clusters occupied by the
|
||||||
|
# refcount table
|
||||||
|
diff = set([c / block_size for c in table_clusters]) - blocks
|
||||||
|
blocks |= diff
|
||||||
|
while len(diff) != 0:
|
||||||
|
# Allocate clusters for new refcount blocks
|
||||||
|
new = self._get_available_clusters((data | clusters) |
|
||||||
|
table_clusters_allocated,
|
||||||
|
len(diff))
|
||||||
|
# Indices of new refcount blocks necessary to cover
|
||||||
|
# clusters in 'new'
|
||||||
|
diff = set([x / block_size for x in new]) - blocks
|
||||||
|
clusters |= new
|
||||||
|
blocks |= diff
|
||||||
|
# Check if the refcount table needs one more cluster
|
||||||
|
if int(ceil((max(blocks) + 1) / float(size))) > table_size:
|
||||||
|
new_block_id = (table_start + table_size) / block_size
|
||||||
|
# Check if the additional table cluster needs
|
||||||
|
# one more refcount block
|
||||||
|
if new_block_id not in blocks:
|
||||||
|
diff.add(new_block_id)
|
||||||
|
table_clusters.add(table_start + table_size)
|
||||||
|
table_size += 1
|
||||||
|
return table_clusters, blocks, clusters
|
||||||
|
|
||||||
|
def create_table_entry(table_offset, block_cluster, block_size,
|
||||||
|
cluster):
|
||||||
|
"""Generate a refcount table entry."""
|
||||||
|
offset = table_offset + UINT64_S * (cluster / block_size)
|
||||||
|
return ['>Q', offset, block_cluster * self.cluster_size,
|
||||||
|
'refcount_table_entry']
|
||||||
|
|
||||||
|
def create_block_entry(block_cluster, block_size, cluster):
|
||||||
|
"""Generate a list of entries for the current block."""
|
||||||
|
entry_size = self.cluster_size / block_size
|
||||||
|
offset = block_cluster * self.cluster_size
|
||||||
|
entry_offset = offset + entry_size * (cluster % block_size)
|
||||||
|
# While snapshots are not supported all refcounts are set to 1
|
||||||
|
return ['>H', entry_offset, 1, 'refcount_block_entry']
|
||||||
|
# Size of a block entry in bits
|
||||||
|
refcount_bits = 1 << self.header['refcount_order'][0].value
|
||||||
|
# Number of refcount entries per refcount block
|
||||||
|
# Convert self.cluster_size from bytes to bits to have the same
|
||||||
|
# base for the numerator and denominator
|
||||||
|
block_size = self.cluster_size * 8 / refcount_bits
|
||||||
|
meta_data = self._get_metadata()
|
||||||
|
if len(self.data_clusters) == 0:
|
||||||
|
# All metadata for an empty guest image needs 4 clusters:
|
||||||
|
# header, rfc table, rfc block, L1 table.
|
||||||
|
# Header takes cluster #0, other clusters ##1-3 can be used
|
||||||
|
block_clusters = set([random.choice(list(set(range(1, 4)) -
|
||||||
|
meta_data))])
|
||||||
|
block_ids = set([0])
|
||||||
|
table_clusters = set([random.choice(list(set(range(1, 4)) -
|
||||||
|
meta_data -
|
||||||
|
block_clusters))])
|
||||||
|
else:
|
||||||
|
block_clusters, block_ids = \
|
||||||
|
allocate_rfc_blocks(self.data_clusters |
|
||||||
|
meta_data, block_size)
|
||||||
|
table_clusters, block_ids, new_clusters = \
|
||||||
|
allocate_rfc_table(self.data_clusters |
|
||||||
|
meta_data |
|
||||||
|
block_clusters,
|
||||||
|
block_ids,
|
||||||
|
block_size)
|
||||||
|
block_clusters |= new_clusters
|
||||||
|
|
||||||
|
meta_data |= block_clusters | table_clusters
|
||||||
|
table_offset = min(table_clusters) * self.cluster_size
|
||||||
|
block_id = None
|
||||||
|
# Clusters allocated for refcount blocks
|
||||||
|
block_clusters = list(block_clusters)
|
||||||
|
# Indices of refcount blocks
|
||||||
|
block_ids = list(block_ids)
|
||||||
|
# Refcount table entries
|
||||||
|
rfc_table = []
|
||||||
|
# Refcount entries
|
||||||
|
rfc_blocks = []
|
||||||
|
|
||||||
|
for cluster in sorted(self.data_clusters | meta_data):
|
||||||
|
if cluster / block_size != block_id:
|
||||||
|
block_id = cluster / block_size
|
||||||
|
block_cluster = block_clusters[block_ids.index(block_id)]
|
||||||
|
rfc_table.append(create_table_entry(table_offset,
|
||||||
|
block_cluster,
|
||||||
|
block_size, cluster))
|
||||||
|
rfc_blocks.append(create_block_entry(block_cluster, block_size,
|
||||||
|
cluster))
|
||||||
|
self.refcount_table = FieldsList(rfc_table)
|
||||||
|
self.refcount_blocks = FieldsList(rfc_blocks)
|
||||||
|
|
||||||
|
self.header['refcount_table_offset'][0].value = table_offset
|
||||||
|
self.header['refcount_table_clusters'][0].value = len(table_clusters)
|
||||||
|
|
||||||
def fuzz(self, fields_to_fuzz=None):
|
def fuzz(self, fields_to_fuzz=None):
|
||||||
"""Fuzz an image by corrupting values of a random subset of its fields.
|
"""Fuzz an image by corrupting values of a random subset of its fields.
|
||||||
|
|
||||||
@ -471,6 +606,7 @@ def create_image(test_img_path, backing_file_name=None, backing_file_fmt=None,
|
|||||||
image.create_feature_name_table()
|
image.create_feature_name_table()
|
||||||
image.set_end_of_extension_area()
|
image.set_end_of_extension_area()
|
||||||
image.create_l_structures()
|
image.create_l_structures()
|
||||||
|
image.create_refcount_structures()
|
||||||
image.fuzz(fields_to_fuzz)
|
image.fuzz(fields_to_fuzz)
|
||||||
image.write(test_img_path)
|
image.write(test_img_path)
|
||||||
return image.image_size
|
return image.image_size
|
||||||
|
@ -70,7 +70,7 @@ def run_app(fd, q_args):
|
|||||||
"""Exception for signal.alarm events."""
|
"""Exception for signal.alarm events."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def handler(*arg):
|
def handler(*args):
|
||||||
"""Notify that an alarm event occurred."""
|
"""Notify that an alarm event occurred."""
|
||||||
raise Alarm
|
raise Alarm
|
||||||
|
|
||||||
@ -134,8 +134,8 @@ class TestEnv(object):
|
|||||||
self.init_path = os.getcwd()
|
self.init_path = os.getcwd()
|
||||||
self.work_dir = work_dir
|
self.work_dir = work_dir
|
||||||
self.current_dir = os.path.join(work_dir, 'test-' + test_id)
|
self.current_dir = os.path.join(work_dir, 'test-' + test_id)
|
||||||
self.qemu_img = os.environ.get('QEMU_IMG', 'qemu-img')\
|
self.qemu_img = \
|
||||||
.strip().split(' ')
|
os.environ.get('QEMU_IMG', 'qemu-img').strip().split(' ')
|
||||||
self.qemu_io = os.environ.get('QEMU_IO', 'qemu-io').strip().split(' ')
|
self.qemu_io = os.environ.get('QEMU_IO', 'qemu-io').strip().split(' ')
|
||||||
self.commands = [['qemu-img', 'check', '-f', 'qcow2', '$test_img'],
|
self.commands = [['qemu-img', 'check', '-f', 'qcow2', '$test_img'],
|
||||||
['qemu-img', 'info', '-f', 'qcow2', '$test_img'],
|
['qemu-img', 'info', '-f', 'qcow2', '$test_img'],
|
||||||
@ -150,8 +150,7 @@ class TestEnv(object):
|
|||||||
'discard $off $len'],
|
'discard $off $len'],
|
||||||
['qemu-io', '$test_img', '-c',
|
['qemu-io', '$test_img', '-c',
|
||||||
'truncate $off']]
|
'truncate $off']]
|
||||||
for fmt in ['raw', 'vmdk', 'vdi', 'cow', 'qcow2', 'file',
|
for fmt in ['raw', 'vmdk', 'vdi', 'qcow2', 'file', 'qed', 'vpc']:
|
||||||
'qed', 'vpc']:
|
|
||||||
self.commands.append(
|
self.commands.append(
|
||||||
['qemu-img', 'convert', '-f', 'qcow2', '-O', fmt,
|
['qemu-img', 'convert', '-f', 'qcow2', '-O', fmt,
|
||||||
'$test_img', 'converted_image.' + fmt])
|
'$test_img', 'converted_image.' + fmt])
|
||||||
@ -178,7 +177,7 @@ class TestEnv(object):
|
|||||||
by 'qemu-img create'.
|
by 'qemu-img create'.
|
||||||
"""
|
"""
|
||||||
# All formats supported by the 'qemu-img create' command.
|
# All formats supported by the 'qemu-img create' command.
|
||||||
backing_file_fmt = random.choice(['raw', 'vmdk', 'vdi', 'cow', 'qcow2',
|
backing_file_fmt = random.choice(['raw', 'vmdk', 'vdi', 'qcow2',
|
||||||
'file', 'qed', 'vpc'])
|
'file', 'qed', 'vpc'])
|
||||||
backing_file_name = 'backing_img.' + backing_file_fmt
|
backing_file_name = 'backing_img.' + backing_file_fmt
|
||||||
backing_file_size = random.randint(MIN_BACKING_FILE_SIZE,
|
backing_file_size = random.randint(MIN_BACKING_FILE_SIZE,
|
||||||
@ -212,10 +211,8 @@ class TestEnv(object):
|
|||||||
|
|
||||||
os.chdir(self.current_dir)
|
os.chdir(self.current_dir)
|
||||||
backing_file_name, backing_file_fmt = self._create_backing_file()
|
backing_file_name, backing_file_fmt = self._create_backing_file()
|
||||||
img_size = image_generator.create_image('test.img',
|
img_size = image_generator.create_image(
|
||||||
backing_file_name,
|
'test.img', backing_file_name, backing_file_fmt, fuzz_config)
|
||||||
backing_file_fmt,
|
|
||||||
fuzz_config)
|
|
||||||
for item in commands:
|
for item in commands:
|
||||||
shutil.copy('test.img', 'copy.img')
|
shutil.copy('test.img', 'copy.img')
|
||||||
# 'off' and 'len' are multiple of the sector size
|
# 'off' and 'len' are multiple of the sector size
|
||||||
@ -228,7 +225,7 @@ class TestEnv(object):
|
|||||||
elif item[0] == 'qemu-io':
|
elif item[0] == 'qemu-io':
|
||||||
current_cmd = list(self.qemu_io)
|
current_cmd = list(self.qemu_io)
|
||||||
else:
|
else:
|
||||||
multilog("Warning: test command '%s' is not defined.\n" \
|
multilog("Warning: test command '%s' is not defined.\n"
|
||||||
% item[0], sys.stderr, self.log, self.parent_log)
|
% item[0], sys.stderr, self.log, self.parent_log)
|
||||||
continue
|
continue
|
||||||
# Replace all placeholders with their real values
|
# Replace all placeholders with their real values
|
||||||
@ -244,29 +241,28 @@ class TestEnv(object):
|
|||||||
"Backing file: %s\n" \
|
"Backing file: %s\n" \
|
||||||
% (self.seed, " ".join(current_cmd),
|
% (self.seed, " ".join(current_cmd),
|
||||||
self.current_dir, backing_file_name)
|
self.current_dir, backing_file_name)
|
||||||
|
|
||||||
temp_log = StringIO.StringIO()
|
temp_log = StringIO.StringIO()
|
||||||
try:
|
try:
|
||||||
retcode = run_app(temp_log, current_cmd)
|
retcode = run_app(temp_log, current_cmd)
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
multilog(test_summary + "Error: Start of '%s' failed. " \
|
multilog("%sError: Start of '%s' failed. Reason: %s\n\n"
|
||||||
"Reason: %s\n\n" % (os.path.basename(
|
% (test_summary, os.path.basename(current_cmd[0]),
|
||||||
current_cmd[0]), e[1]),
|
e[1]),
|
||||||
sys.stderr, self.log, self.parent_log)
|
sys.stderr, self.log, self.parent_log)
|
||||||
raise TestException
|
raise TestException
|
||||||
|
|
||||||
if retcode < 0:
|
if retcode < 0:
|
||||||
self.log.write(temp_log.getvalue())
|
self.log.write(temp_log.getvalue())
|
||||||
multilog(test_summary + "FAIL: Test terminated by signal " +
|
multilog("%sFAIL: Test terminated by signal %s\n\n"
|
||||||
"%s\n\n" % str_signal(-retcode), sys.stderr, self.log,
|
% (test_summary, str_signal(-retcode)),
|
||||||
self.parent_log)
|
sys.stderr, self.log, self.parent_log)
|
||||||
self.failed = True
|
self.failed = True
|
||||||
else:
|
else:
|
||||||
if self.log_all:
|
if self.log_all:
|
||||||
self.log.write(temp_log.getvalue())
|
self.log.write(temp_log.getvalue())
|
||||||
multilog(test_summary + "PASS: Application exited with" +
|
multilog("%sPASS: Application exited with the code " \
|
||||||
" the code '%d'\n\n" % retcode, sys.stdout,
|
"'%d'\n\n" % (test_summary, retcode),
|
||||||
self.log, self.parent_log)
|
sys.stdout, self.log, self.parent_log)
|
||||||
temp_log.close()
|
temp_log.close()
|
||||||
os.remove('copy.img')
|
os.remove('copy.img')
|
||||||
|
|
||||||
@ -286,8 +282,9 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
Set up test environment in TEST_DIR and run a test in it. A module for
|
Set up test environment in TEST_DIR and run a test in it. A module for
|
||||||
test image generation should be specified via IMG_GENERATOR.
|
test image generation should be specified via IMG_GENERATOR.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
runner.py -c '[["qemu-img", "info", "$test_img"]]' /tmp/test qcow2
|
runner.py -c '[["qemu-img", "info", "$test_img"]]' /tmp/test qcow2
|
||||||
|
|
||||||
Optional arguments:
|
Optional arguments:
|
||||||
-h, --help display this help and exit
|
-h, --help display this help and exit
|
||||||
@ -305,20 +302,22 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
'--command' accepts a JSON array of commands. Each command presents
|
'--command' accepts a JSON array of commands. Each command presents
|
||||||
an application under test with all its paramaters as a list of strings,
|
an application under test with all its paramaters as a list of strings,
|
||||||
e.g.
|
e.g. ["qemu-io", "$test_img", "-c", "write $off $len"].
|
||||||
["qemu-io", "$test_img", "-c", "write $off $len"]
|
|
||||||
|
|
||||||
Supported application aliases: 'qemu-img' and 'qemu-io'.
|
Supported application aliases: 'qemu-img' and 'qemu-io'.
|
||||||
|
|
||||||
Supported argument aliases: $test_img for the fuzzed image, $off
|
Supported argument aliases: $test_img for the fuzzed image, $off
|
||||||
for an offset, $len for length.
|
for an offset, $len for length.
|
||||||
|
|
||||||
Values for $off and $len will be generated based on the virtual disk
|
Values for $off and $len will be generated based on the virtual disk
|
||||||
size of the fuzzed image
|
size of the fuzzed image.
|
||||||
|
|
||||||
Paths to 'qemu-img' and 'qemu-io' are retrevied from 'QEMU_IMG' and
|
Paths to 'qemu-img' and 'qemu-io' are retrevied from 'QEMU_IMG' and
|
||||||
'QEMU_IO' environment variables
|
'QEMU_IO' environment variables.
|
||||||
|
|
||||||
'--config' accepts a JSON array of fields to be fuzzed, e.g.
|
'--config' accepts a JSON array of fields to be fuzzed, e.g.
|
||||||
'[["header"], ["header", "version"]]'
|
'[["header"], ["header", "version"]]'.
|
||||||
|
|
||||||
Each of the list elements can consist of a complex image element only
|
Each of the list elements can consist of a complex image element only
|
||||||
as ["header"] or ["feature_name_table"] or an exact field as
|
as ["header"] or ["feature_name_table"] or an exact field as
|
||||||
["header", "version"]. In the first case random portion of the element
|
["header", "version"]. In the first case random portion of the element
|
||||||
@ -368,7 +367,6 @@ if __name__ == '__main__':
|
|||||||
seed = None
|
seed = None
|
||||||
config = None
|
config = None
|
||||||
duration = None
|
duration = None
|
||||||
|
|
||||||
for opt, arg in opts:
|
for opt, arg in opts:
|
||||||
if opt in ('-h', '--help'):
|
if opt in ('-h', '--help'):
|
||||||
usage()
|
usage()
|
||||||
|
@ -71,6 +71,12 @@ void qpci_device_enable(QPCIDevice *dev)
|
|||||||
cmd = qpci_config_readw(dev, PCI_COMMAND);
|
cmd = qpci_config_readw(dev, PCI_COMMAND);
|
||||||
cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
|
cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
|
||||||
qpci_config_writew(dev, PCI_COMMAND, cmd);
|
qpci_config_writew(dev, PCI_COMMAND, cmd);
|
||||||
|
|
||||||
|
/* Verify the bits are now set. */
|
||||||
|
cmd = qpci_config_readw(dev, PCI_COMMAND);
|
||||||
|
g_assert_cmphex(cmd & PCI_COMMAND_IO, ==, PCI_COMMAND_IO);
|
||||||
|
g_assert_cmphex(cmd & PCI_COMMAND_MEMORY, ==, PCI_COMMAND_MEMORY);
|
||||||
|
g_assert_cmphex(cmd & PCI_COMMAND_MASTER, ==, PCI_COMMAND_MASTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id)
|
uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id)
|
||||||
|
@ -169,9 +169,61 @@ echo "=== Testing unallocated image header ==="
|
|||||||
echo
|
echo
|
||||||
_make_test_img 64M
|
_make_test_img 64M
|
||||||
# Create L1/L2
|
# Create L1/L2
|
||||||
$QEMU_IO -c "$OPEN_RW" -c "write 0 64k" | _filter_qemu_io
|
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
poke_file "$TEST_IMG" "$rb_offset" "\x00\x00"
|
poke_file "$TEST_IMG" "$rb_offset" "\x00\x00"
|
||||||
$QEMU_IO -c "$OPEN_RW" -c "write 64k 64k" | _filter_qemu_io
|
$QEMU_IO -c "write 64k 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Testing unaligned L1 entry ==="
|
||||||
|
echo
|
||||||
|
_make_test_img 64M
|
||||||
|
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
# This will be masked with ~(512 - 1) = ~0x1ff, so whether the lower 9 bits are
|
||||||
|
# aligned or not does not matter
|
||||||
|
poke_file "$TEST_IMG" "$l1_offset" "\x80\x00\x00\x00\x00\x04\x2a\x00"
|
||||||
|
$QEMU_IO -c "read 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Testing unaligned L2 entry ==="
|
||||||
|
echo
|
||||||
|
_make_test_img 64M
|
||||||
|
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x05\x2a\x00"
|
||||||
|
$QEMU_IO -c "read 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Testing unaligned reftable entry ==="
|
||||||
|
echo
|
||||||
|
_make_test_img 64M
|
||||||
|
poke_file "$TEST_IMG" "$rt_offset" "\x00\x00\x00\x00\x00\x02\x2a\x00"
|
||||||
|
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Testing non-fatal corruption on freeing ==="
|
||||||
|
echo
|
||||||
|
_make_test_img 64M
|
||||||
|
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x05\x2a\x00"
|
||||||
|
$QEMU_IO -c "discard 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Testing read-only corruption report ==="
|
||||||
|
echo
|
||||||
|
_make_test_img 64M
|
||||||
|
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x05\x2a\x00"
|
||||||
|
# Should only emit a single error message
|
||||||
|
$QEMU_IO -c "$OPEN_RO" -c "read 0 64k" -c "read 0 64k" | _filter_qemu_io
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Testing non-fatal and then fatal corruption report ==="
|
||||||
|
echo
|
||||||
|
_make_test_img 64M
|
||||||
|
$QEMU_IO -c "write 0 128k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x05\x2a\x00"
|
||||||
|
poke_file "$TEST_IMG" "$(($l2_offset+8))" "\x80\x00\x00\x00\x00\x06\x2a\x00"
|
||||||
|
# Should emit two error messages
|
||||||
|
$QEMU_IO -c "discard 0 64k" -c "read 64k 64k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
|
||||||
# success, all done
|
# success, all done
|
||||||
echo "*** done"
|
echo "*** done"
|
||||||
|
@ -8,7 +8,7 @@ ERROR cluster 3 refcount=1 reference=3
|
|||||||
1 errors were found on the image.
|
1 errors were found on the image.
|
||||||
Data may be corrupted, or further writes to the image may corrupt it.
|
Data may be corrupted, or further writes to the image may corrupt it.
|
||||||
incompatible_features 0x0
|
incompatible_features 0x0
|
||||||
qcow2: Preventing invalid write on metadata (overlaps with active L1 table); image marked as corrupt.
|
qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with active L1 table); further corruption events will be suppressed
|
||||||
write failed: Input/output error
|
write failed: Input/output error
|
||||||
incompatible_features 0x2
|
incompatible_features 0x2
|
||||||
qemu-io: can't open device TEST_DIR/t.IMGFMT: IMGFMT: Image is corrupt; cannot be opened read/write
|
qemu-io: can't open device TEST_DIR/t.IMGFMT: IMGFMT: Image is corrupt; cannot be opened read/write
|
||||||
@ -24,7 +24,7 @@ ERROR cluster 2 refcount=1 reference=2
|
|||||||
2 errors were found on the image.
|
2 errors were found on the image.
|
||||||
Data may be corrupted, or further writes to the image may corrupt it.
|
Data may be corrupted, or further writes to the image may corrupt it.
|
||||||
incompatible_features 0x0
|
incompatible_features 0x0
|
||||||
qcow2: Preventing invalid write on metadata (overlaps with refcount block); image marked as corrupt.
|
qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with refcount block); further corruption events will be suppressed
|
||||||
write failed: Input/output error
|
write failed: Input/output error
|
||||||
incompatible_features 0x2
|
incompatible_features 0x2
|
||||||
Repairing refcount block 0 refcount=2
|
Repairing refcount block 0 refcount=2
|
||||||
@ -56,7 +56,7 @@ Data may be corrupted, or further writes to the image may corrupt it.
|
|||||||
1 leaked clusters were found on the image.
|
1 leaked clusters were found on the image.
|
||||||
This means waste of disk space, but no harm to data.
|
This means waste of disk space, but no harm to data.
|
||||||
incompatible_features 0x0
|
incompatible_features 0x0
|
||||||
qcow2: Preventing invalid write on metadata (overlaps with inactive L2 table); image marked as corrupt.
|
qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with inactive L2 table); further corruption events will be suppressed
|
||||||
write failed: Input/output error
|
write failed: Input/output error
|
||||||
incompatible_features 0x2
|
incompatible_features 0x2
|
||||||
Repairing cluster 4 refcount=1 reference=2
|
Repairing cluster 4 refcount=1 reference=2
|
||||||
@ -88,7 +88,7 @@ wrote 65536/65536 bytes at offset 536870912
|
|||||||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
discard 65536/65536 bytes at offset 0
|
discard 65536/65536 bytes at offset 0
|
||||||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
qcow2: Preventing invalid write on metadata (overlaps with active L2 table); image marked as corrupt.
|
qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with active L2 table); further corruption events will be suppressed
|
||||||
blkdebug: Suspended request '0'
|
blkdebug: Suspended request '0'
|
||||||
write failed: Input/output error
|
write failed: Input/output error
|
||||||
blkdebug: Resuming request '0'
|
blkdebug: Resuming request '0'
|
||||||
@ -99,6 +99,57 @@ aio_write failed: No medium found
|
|||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
wrote 65536/65536 bytes at offset 0
|
wrote 65536/65536 bytes at offset 0
|
||||||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
qcow2: Preventing invalid write on metadata (overlaps with qcow2_header); image marked as corrupt.
|
qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with qcow2_header); further corruption events will be suppressed
|
||||||
write failed: Input/output error
|
write failed: Input/output error
|
||||||
|
|
||||||
|
=== Testing unaligned L1 entry ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
wrote 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
qcow2: Marking image as corrupt: L2 table offset 0x42a00 unaligned (L1 index: 0); further corruption events will be suppressed
|
||||||
|
read failed: Input/output error
|
||||||
|
|
||||||
|
=== Testing unaligned L2 entry ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
wrote 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
qcow2: Marking image as corrupt: Data cluster offset 0x52a00 unaligned (L2 offset: 0x40000, L2 index: 0); further corruption events will be suppressed
|
||||||
|
read failed: Input/output error
|
||||||
|
|
||||||
|
=== Testing unaligned reftable entry ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
qcow2: Marking image as corrupt: Refblock offset 0x22a00 unaligned (reftable index: 0); further corruption events will be suppressed
|
||||||
|
write failed: Input/output error
|
||||||
|
|
||||||
|
=== Testing non-fatal corruption on freeing ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
wrote 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
qcow2: Image is corrupt: Cannot free unaligned cluster 0x52a00; further non-fatal corruption events will be suppressed
|
||||||
|
discard 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
=== Testing read-only corruption report ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
wrote 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
qcow2: Image is corrupt: Data cluster offset 0x52a00 unaligned (L2 offset: 0x40000, L2 index: 0); further non-fatal corruption events will be suppressed
|
||||||
|
read failed: Input/output error
|
||||||
|
read failed: Input/output error
|
||||||
|
|
||||||
|
=== Testing non-fatal and then fatal corruption report ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
wrote 131072/131072 bytes at offset 0
|
||||||
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
qcow2: Image is corrupt: Cannot free unaligned cluster 0x52a00; further non-fatal corruption events will be suppressed
|
||||||
|
qcow2: Marking image as corrupt: Data cluster offset 0x62a00 unaligned (L2 offset: 0x40000, L2 index: 0x1); further corruption events will be suppressed
|
||||||
|
discard 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read failed: Input/output error
|
||||||
*** done
|
*** done
|
||||||
|
@ -38,7 +38,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
|
|||||||
. ./common.rc
|
. ./common.rc
|
||||||
. ./common.filter
|
. ./common.filter
|
||||||
|
|
||||||
_supported_fmt cow qed qcow qcow2 vmdk
|
_supported_fmt qed qcow qcow2 vmdk
|
||||||
_supported_proto file
|
_supported_proto file
|
||||||
_supported_os Linux
|
_supported_os Linux
|
||||||
_unsupported_imgopts "subformat=monolithicFlat" "subformat=twoGbMaxExtentFlat"
|
_unsupported_imgopts "subformat=monolithicFlat" "subformat=twoGbMaxExtentFlat"
|
||||||
|
@ -38,7 +38,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
|
|||||||
. ./common.rc
|
. ./common.rc
|
||||||
. ./common.filter
|
. ./common.filter
|
||||||
|
|
||||||
_supported_fmt vpc vmdk vhdx vdi qed qcow2 qcow cow
|
_supported_fmt vpc vmdk vhdx vdi qed qcow2 qcow
|
||||||
_supported_proto file
|
_supported_proto file
|
||||||
_supported_os Linux
|
_supported_os Linux
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
|
|||||||
|
|
||||||
# Basically all formats, but "raw" has issues with _filter_imgfmt regarding the
|
# Basically all formats, but "raw" has issues with _filter_imgfmt regarding the
|
||||||
# raw comparison image for blkverify; also, all images have to support creation
|
# raw comparison image for blkverify; also, all images have to support creation
|
||||||
_supported_fmt cow qcow qcow2 qed vdi vhdx vmdk vpc
|
_supported_fmt qcow qcow2 qed vdi vhdx vmdk vpc
|
||||||
_supported_proto file
|
_supported_proto file
|
||||||
_supported_os Linux
|
_supported_os Linux
|
||||||
|
|
||||||
|
@ -136,7 +136,6 @@ common options
|
|||||||
check options
|
check options
|
||||||
-raw test raw (default)
|
-raw test raw (default)
|
||||||
-bochs test bochs
|
-bochs test bochs
|
||||||
-cow test cow
|
|
||||||
-cloop test cloop
|
-cloop test cloop
|
||||||
-parallels test parallels
|
-parallels test parallels
|
||||||
-qcow test qcow
|
-qcow test qcow
|
||||||
@ -182,11 +181,6 @@ testlist options
|
|||||||
xpand=false
|
xpand=false
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-cow)
|
|
||||||
IMGFMT=cow
|
|
||||||
xpand=false
|
|
||||||
;;
|
|
||||||
|
|
||||||
-cloop)
|
-cloop)
|
||||||
IMGFMT=cloop
|
IMGFMT=cloop
|
||||||
IMGFMT_GENERIC=false
|
IMGFMT_GENERIC=false
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "block/aio.h"
|
#include "block/aio.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "qemu/sockets.h"
|
#include "qemu/sockets.h"
|
||||||
|
#include "qemu/error-report.h"
|
||||||
|
|
||||||
static AioContext *ctx;
|
static AioContext *ctx;
|
||||||
|
|
||||||
@ -810,11 +811,18 @@ static void test_source_timer_schedule(void)
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
Error *local_error = NULL;
|
||||||
GSource *src;
|
GSource *src;
|
||||||
|
|
||||||
init_clocks();
|
init_clocks();
|
||||||
|
|
||||||
ctx = aio_context_new();
|
ctx = aio_context_new(&local_error);
|
||||||
|
if (!ctx) {
|
||||||
|
error_report("Failed to create AIO Context: '%s'",
|
||||||
|
error_get_pretty(local_error));
|
||||||
|
error_free(local_error);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
src = aio_get_g_source(ctx);
|
src = aio_get_g_source(ctx);
|
||||||
g_source_attach(src, NULL);
|
g_source_attach(src, NULL);
|
||||||
g_source_unref(src);
|
g_source_unref(src);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "block/thread-pool.h"
|
#include "block/thread-pool.h"
|
||||||
#include "block/block.h"
|
#include "block/block.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
|
#include "qemu/error-report.h"
|
||||||
|
|
||||||
static AioContext *ctx;
|
static AioContext *ctx;
|
||||||
static ThreadPool *pool;
|
static ThreadPool *pool;
|
||||||
@ -33,7 +34,7 @@ static int long_cb(void *opaque)
|
|||||||
static void done_cb(void *opaque, int ret)
|
static void done_cb(void *opaque, int ret)
|
||||||
{
|
{
|
||||||
WorkerTestData *data = opaque;
|
WorkerTestData *data = opaque;
|
||||||
g_assert_cmpint(data->ret, ==, -EINPROGRESS);
|
g_assert(data->ret == -EINPROGRESS || data->ret == -ECANCELED);
|
||||||
data->ret = ret;
|
data->ret = ret;
|
||||||
data->aiocb = NULL;
|
data->aiocb = NULL;
|
||||||
|
|
||||||
@ -132,7 +133,7 @@ static void test_submit_many(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_cancel(void)
|
static void do_test_cancel(bool sync)
|
||||||
{
|
{
|
||||||
WorkerTestData data[100];
|
WorkerTestData data[100];
|
||||||
int num_canceled;
|
int num_canceled;
|
||||||
@ -170,18 +171,25 @@ static void test_cancel(void)
|
|||||||
for (i = 0; i < 100; i++) {
|
for (i = 0; i < 100; i++) {
|
||||||
if (atomic_cmpxchg(&data[i].n, 0, 3) == 0) {
|
if (atomic_cmpxchg(&data[i].n, 0, 3) == 0) {
|
||||||
data[i].ret = -ECANCELED;
|
data[i].ret = -ECANCELED;
|
||||||
bdrv_aio_cancel(data[i].aiocb);
|
if (sync) {
|
||||||
active--;
|
bdrv_aio_cancel(data[i].aiocb);
|
||||||
|
} else {
|
||||||
|
bdrv_aio_cancel_async(data[i].aiocb);
|
||||||
|
}
|
||||||
num_canceled++;
|
num_canceled++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_assert_cmpint(active, >, 0);
|
g_assert_cmpint(active, >, 0);
|
||||||
g_assert_cmpint(num_canceled, <, 100);
|
g_assert_cmpint(num_canceled, <, 100);
|
||||||
|
|
||||||
/* Canceling the others will be a blocking operation. */
|
|
||||||
for (i = 0; i < 100; i++) {
|
for (i = 0; i < 100; i++) {
|
||||||
if (data[i].aiocb && data[i].n != 3) {
|
if (data[i].aiocb && data[i].n != 3) {
|
||||||
bdrv_aio_cancel(data[i].aiocb);
|
if (sync) {
|
||||||
|
/* Canceling the others will be a blocking operation. */
|
||||||
|
bdrv_aio_cancel(data[i].aiocb);
|
||||||
|
} else {
|
||||||
|
bdrv_aio_cancel_async(data[i].aiocb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,22 +201,39 @@ static void test_cancel(void)
|
|||||||
for (i = 0; i < 100; i++) {
|
for (i = 0; i < 100; i++) {
|
||||||
if (data[i].n == 3) {
|
if (data[i].n == 3) {
|
||||||
g_assert_cmpint(data[i].ret, ==, -ECANCELED);
|
g_assert_cmpint(data[i].ret, ==, -ECANCELED);
|
||||||
g_assert(data[i].aiocb != NULL);
|
g_assert(data[i].aiocb == NULL);
|
||||||
} else {
|
} else {
|
||||||
g_assert_cmpint(data[i].n, ==, 2);
|
g_assert_cmpint(data[i].n, ==, 2);
|
||||||
g_assert_cmpint(data[i].ret, ==, 0);
|
g_assert(data[i].ret == 0 || data[i].ret == -ECANCELED);
|
||||||
g_assert(data[i].aiocb == NULL);
|
g_assert(data[i].aiocb == NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_cancel(void)
|
||||||
|
{
|
||||||
|
do_test_cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_cancel_async(void)
|
||||||
|
{
|
||||||
|
do_test_cancel(false);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
Error *local_error = NULL;
|
||||||
|
|
||||||
init_clocks();
|
init_clocks();
|
||||||
|
|
||||||
ctx = aio_context_new();
|
ctx = aio_context_new(&local_error);
|
||||||
|
if (!ctx) {
|
||||||
|
error_report("Failed to create AIO Context: '%s'",
|
||||||
|
error_get_pretty(local_error));
|
||||||
|
error_free(local_error);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
pool = aio_get_thread_pool(ctx);
|
pool = aio_get_thread_pool(ctx);
|
||||||
|
|
||||||
g_test_init(&argc, &argv, NULL);
|
g_test_init(&argc, &argv, NULL);
|
||||||
@ -217,6 +242,7 @@ int main(int argc, char **argv)
|
|||||||
g_test_add_func("/thread-pool/submit-co", test_submit_co);
|
g_test_add_func("/thread-pool/submit-co", test_submit_co);
|
||||||
g_test_add_func("/thread-pool/submit-many", test_submit_many);
|
g_test_add_func("/thread-pool/submit-many", test_submit_many);
|
||||||
g_test_add_func("/thread-pool/cancel", test_cancel);
|
g_test_add_func("/thread-pool/cancel", test_cancel);
|
||||||
|
g_test_add_func("/thread-pool/cancel-async", test_cancel_async);
|
||||||
|
|
||||||
ret = g_test_run();
|
ret = g_test_run();
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include "block/aio.h"
|
#include "block/aio.h"
|
||||||
#include "qemu/throttle.h"
|
#include "qemu/throttle.h"
|
||||||
|
#include "qemu/error-report.h"
|
||||||
|
|
||||||
static AioContext *ctx;
|
static AioContext *ctx;
|
||||||
static LeakyBucket bkt;
|
static LeakyBucket bkt;
|
||||||
@ -492,10 +493,17 @@ static void test_accounting(void)
|
|||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
GSource *src;
|
GSource *src;
|
||||||
|
Error *local_error = NULL;
|
||||||
|
|
||||||
init_clocks();
|
init_clocks();
|
||||||
|
|
||||||
ctx = aio_context_new();
|
ctx = aio_context_new(&local_error);
|
||||||
|
if (!ctx) {
|
||||||
|
error_report("Failed to create AIO Context: '%s'",
|
||||||
|
error_get_pretty(local_error));
|
||||||
|
error_free(local_error);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
src = aio_get_g_source(ctx);
|
src = aio_get_g_source(ctx);
|
||||||
g_source_attach(src, NULL);
|
g_source_attach(src, NULL);
|
||||||
g_source_unref(src);
|
g_source_unref(src);
|
||||||
|
@ -31,7 +31,6 @@ enum ThreadState {
|
|||||||
THREAD_QUEUED,
|
THREAD_QUEUED,
|
||||||
THREAD_ACTIVE,
|
THREAD_ACTIVE,
|
||||||
THREAD_DONE,
|
THREAD_DONE,
|
||||||
THREAD_CANCELED,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ThreadPoolElement {
|
struct ThreadPoolElement {
|
||||||
@ -58,7 +57,6 @@ struct ThreadPool {
|
|||||||
AioContext *ctx;
|
AioContext *ctx;
|
||||||
QEMUBH *completion_bh;
|
QEMUBH *completion_bh;
|
||||||
QemuMutex lock;
|
QemuMutex lock;
|
||||||
QemuCond check_cancel;
|
|
||||||
QemuCond worker_stopped;
|
QemuCond worker_stopped;
|
||||||
QemuSemaphore sem;
|
QemuSemaphore sem;
|
||||||
int max_threads;
|
int max_threads;
|
||||||
@ -73,7 +71,6 @@ struct ThreadPool {
|
|||||||
int idle_threads;
|
int idle_threads;
|
||||||
int new_threads; /* backlog of threads we need to create */
|
int new_threads; /* backlog of threads we need to create */
|
||||||
int pending_threads; /* threads created but not running yet */
|
int pending_threads; /* threads created but not running yet */
|
||||||
int pending_cancellations; /* whether we need a cond_broadcast */
|
|
||||||
bool stopping;
|
bool stopping;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,9 +110,6 @@ static void *worker_thread(void *opaque)
|
|||||||
req->state = THREAD_DONE;
|
req->state = THREAD_DONE;
|
||||||
|
|
||||||
qemu_mutex_lock(&pool->lock);
|
qemu_mutex_lock(&pool->lock);
|
||||||
if (pool->pending_cancellations) {
|
|
||||||
qemu_cond_broadcast(&pool->check_cancel);
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_bh_schedule(pool->completion_bh);
|
qemu_bh_schedule(pool->completion_bh);
|
||||||
}
|
}
|
||||||
@ -173,7 +167,7 @@ static void thread_pool_completion_bh(void *opaque)
|
|||||||
|
|
||||||
restart:
|
restart:
|
||||||
QLIST_FOREACH_SAFE(elem, &pool->head, all, next) {
|
QLIST_FOREACH_SAFE(elem, &pool->head, all, next) {
|
||||||
if (elem->state != THREAD_CANCELED && elem->state != THREAD_DONE) {
|
if (elem->state != THREAD_DONE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (elem->state == THREAD_DONE) {
|
if (elem->state == THREAD_DONE) {
|
||||||
@ -191,12 +185,12 @@ restart:
|
|||||||
qemu_bh_schedule(pool->completion_bh);
|
qemu_bh_schedule(pool->completion_bh);
|
||||||
|
|
||||||
elem->common.cb(elem->common.opaque, elem->ret);
|
elem->common.cb(elem->common.opaque, elem->ret);
|
||||||
qemu_aio_release(elem);
|
qemu_aio_unref(elem);
|
||||||
goto restart;
|
goto restart;
|
||||||
} else {
|
} else {
|
||||||
/* remove the request */
|
/* remove the request */
|
||||||
QLIST_REMOVE(elem, all);
|
QLIST_REMOVE(elem, all);
|
||||||
qemu_aio_release(elem);
|
qemu_aio_unref(elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,22 +211,26 @@ static void thread_pool_cancel(BlockDriverAIOCB *acb)
|
|||||||
*/
|
*/
|
||||||
qemu_sem_timedwait(&pool->sem, 0) == 0) {
|
qemu_sem_timedwait(&pool->sem, 0) == 0) {
|
||||||
QTAILQ_REMOVE(&pool->request_list, elem, reqs);
|
QTAILQ_REMOVE(&pool->request_list, elem, reqs);
|
||||||
elem->state = THREAD_CANCELED;
|
|
||||||
qemu_bh_schedule(pool->completion_bh);
|
qemu_bh_schedule(pool->completion_bh);
|
||||||
} else {
|
|
||||||
pool->pending_cancellations++;
|
elem->state = THREAD_DONE;
|
||||||
while (elem->state != THREAD_CANCELED && elem->state != THREAD_DONE) {
|
elem->ret = -ECANCELED;
|
||||||
qemu_cond_wait(&pool->check_cancel, &pool->lock);
|
|
||||||
}
|
|
||||||
pool->pending_cancellations--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_mutex_unlock(&pool->lock);
|
qemu_mutex_unlock(&pool->lock);
|
||||||
thread_pool_completion_bh(pool);
|
}
|
||||||
|
|
||||||
|
static AioContext *thread_pool_get_aio_context(BlockDriverAIOCB *acb)
|
||||||
|
{
|
||||||
|
ThreadPoolElement *elem = (ThreadPoolElement *)acb;
|
||||||
|
ThreadPool *pool = elem->pool;
|
||||||
|
return pool->ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const AIOCBInfo thread_pool_aiocb_info = {
|
static const AIOCBInfo thread_pool_aiocb_info = {
|
||||||
.aiocb_size = sizeof(ThreadPoolElement),
|
.aiocb_size = sizeof(ThreadPoolElement),
|
||||||
.cancel = thread_pool_cancel,
|
.cancel_async = thread_pool_cancel,
|
||||||
|
.get_aio_context = thread_pool_get_aio_context,
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockDriverAIOCB *thread_pool_submit_aio(ThreadPool *pool,
|
BlockDriverAIOCB *thread_pool_submit_aio(ThreadPool *pool,
|
||||||
@ -299,7 +297,6 @@ static void thread_pool_init_one(ThreadPool *pool, AioContext *ctx)
|
|||||||
pool->ctx = ctx;
|
pool->ctx = ctx;
|
||||||
pool->completion_bh = aio_bh_new(ctx, thread_pool_completion_bh, pool);
|
pool->completion_bh = aio_bh_new(ctx, thread_pool_completion_bh, pool);
|
||||||
qemu_mutex_init(&pool->lock);
|
qemu_mutex_init(&pool->lock);
|
||||||
qemu_cond_init(&pool->check_cancel);
|
|
||||||
qemu_cond_init(&pool->worker_stopped);
|
qemu_cond_init(&pool->worker_stopped);
|
||||||
qemu_sem_init(&pool->sem, 0);
|
qemu_sem_init(&pool->sem, 0);
|
||||||
pool->max_threads = 64;
|
pool->max_threads = 64;
|
||||||
@ -342,7 +339,6 @@ void thread_pool_free(ThreadPool *pool)
|
|||||||
|
|
||||||
qemu_bh_delete(pool->completion_bh);
|
qemu_bh_delete(pool->completion_bh);
|
||||||
qemu_sem_destroy(&pool->sem);
|
qemu_sem_destroy(&pool->sem);
|
||||||
qemu_cond_destroy(&pool->check_cancel);
|
|
||||||
qemu_cond_destroy(&pool->worker_stopped);
|
qemu_cond_destroy(&pool->worker_stopped);
|
||||||
qemu_mutex_destroy(&pool->lock);
|
qemu_mutex_destroy(&pool->lock);
|
||||||
g_free(pool);
|
g_free(pool);
|
||||||
|
@ -857,7 +857,7 @@ QemuCocoaView *cocoaView;
|
|||||||
[op setPrompt:@"Boot image"];
|
[op setPrompt:@"Boot image"];
|
||||||
[op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"];
|
[op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"];
|
||||||
NSArray *filetypes = [NSArray arrayWithObjects:@"img", @"iso", @"dmg",
|
NSArray *filetypes = [NSArray arrayWithObjects:@"img", @"iso", @"dmg",
|
||||||
@"qcow", @"qcow2", @"cow", @"cloop", @"vmdk", nil];
|
@"qcow", @"qcow2", @"cloop", @"vmdk", nil];
|
||||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
|
||||||
[op setAllowedFileTypes:filetypes];
|
[op setAllowedFileTypes:filetypes];
|
||||||
[op beginSheetModalForWindow:normalWindow
|
[op beginSheetModalForWindow:normalWindow
|
||||||
|
5
vl.c
5
vl.c
@ -2968,6 +2968,7 @@ int main(int argc, char **argv, char **envp)
|
|||||||
ram_addr_t maxram_size = default_ram_size;
|
ram_addr_t maxram_size = default_ram_size;
|
||||||
uint64_t ram_slots = 0;
|
uint64_t ram_slots = 0;
|
||||||
FILE *vmstate_dump_file = NULL;
|
FILE *vmstate_dump_file = NULL;
|
||||||
|
Error *main_loop_err = NULL;
|
||||||
|
|
||||||
atexit(qemu_run_exit_notifiers);
|
atexit(qemu_run_exit_notifiers);
|
||||||
error_set_progname(argv[0]);
|
error_set_progname(argv[0]);
|
||||||
@ -3998,8 +3999,8 @@ int main(int argc, char **argv, char **envp)
|
|||||||
|
|
||||||
os_daemonize();
|
os_daemonize();
|
||||||
|
|
||||||
if (qemu_init_main_loop()) {
|
if (qemu_init_main_loop(&main_loop_err)) {
|
||||||
fprintf(stderr, "qemu_init_main_loop failed\n");
|
error_report("%s", error_get_pretty(main_loop_err));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user