mirror of https://github.com/xqemu/xqemu.git
Block patches
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJVP5qZAAoJEH8JsnLIjy/WpUoP/2T7iyDQgcqvbLbnkoOz+Kq6 /bll6VhLDzkRmgi3talyNOhEu0jAzvHq4BmA76WSxKme7/ykSx1skYsVmbRzLqcO 4pun8Y+Ym074z5A65JgkuwOsa8ZhVwVtd4JkAjmeG5wZZsdJkK1YXt805eDYlAaf zA0J0oSVm4NqOAfqRI4NfWWYHQ16L6oPpgQ5e45ljcbh2nfNaZCr8qk1xZW5x3/K 62DnOdX5cmBClemxeM8uy2u6sfFMy2+DKyuY4hNoRhDKbulYgrRTLZRTNCnZw44Y ZjaqudxV4DR4tEalhEkLsy1LGt+I/saP1M/jtZzwg2tRbQi6vQGNYTrlLJjMXDkW MyACmdtIc5zmsrPDiKLjEg3h3pRn5vj6TheOtIN4ILZ/elSuJbKgV7M5f3isrBZw czavCjKkVCKe4L+beMZ1yNRpy1mSbpj1Zlxxpawok6isOxkyqFtqKidiq5g2F6r+ zGEk2ZBRlYQHyc979ZdbKtOEFFjzli0WjN2ul7ObhhkPKT8bRPuU8fPgwBQuoahH 5NM/pncB6AusY9b50GiPKEmtHnf86KO/1MWOp/2eLq4oyHt0pgPpwAiIeeu24Z5a pBvsz2+a4ysvl+g8JYIgrg+ME2ym4UTr+BX+kYEU54X2NZE7A7iCmmYd8yai/hJ8 Nu783KGxQV0iScJwqnVK =rl85 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block patches # gpg: Signature made Tue Apr 28 15:35:05 2015 BST using RSA key ID C88F2FD6 # gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" * remotes/kevin/tags/for-upstream: (76 commits) block: move I/O request processing to block/io.c block: extract bdrv_setup_io_funcs() block: add bdrv_set_dirty()/bdrv_reset_dirty() to block_int.h block: replace bdrv_states iteration with bdrv_next() vmdk: Widen before shifting 32 bit header field block/dmg: make it modular block/mirror: Always call block_job_sleep_ns() iotests: add incremental backup granularity tests iotests: add incremental backup failure recovery test iotests: add simple incremental backup case iotests: add QMP event waiting queue iotests: add invalid input incremental backup tests hbitmap: truncate tests block: Resize bitmaps on bdrv_truncate block: Ensure consistent bitmap function prototypes block: add BdrvDirtyBitmap documentation qmp: Add dirty bitmap status field in query-block qmp: add block-dirty-bitmap-clear qmp: Add support of "dirty-bitmap" sync mode for drive-backup block: Add bitmap successors ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
a9392bc93c
12
MAINTAINERS
12
MAINTAINERS
|
@ -1182,7 +1182,19 @@ S: Supported
|
||||||
F: block/gluster.c
|
F: block/gluster.c
|
||||||
T: git git://github.com/codyprime/qemu-kvm-jtc.git block
|
T: git git://github.com/codyprime/qemu-kvm-jtc.git block
|
||||||
|
|
||||||
|
Null Block Driver
|
||||||
|
M: Fam Zheng <famz@redhat.com>
|
||||||
|
L: qemu-block@nongnu.org
|
||||||
|
S: Supported
|
||||||
|
F: block/null.c
|
||||||
|
|
||||||
Bootdevice
|
Bootdevice
|
||||||
M: Gonglei <arei.gonglei@huawei.com>
|
M: Gonglei <arei.gonglei@huawei.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: bootdevice.c
|
F: bootdevice.c
|
||||||
|
|
||||||
|
Quorum
|
||||||
|
M: Alberto Garcia <berto@igalia.com>
|
||||||
|
S: Supported
|
||||||
|
F: block/quorum.c
|
||||||
|
L: qemu-block@nongnu.org
|
||||||
|
|
87
aio-posix.c
87
aio-posix.c
|
@ -24,7 +24,6 @@ struct AioHandler
|
||||||
IOHandler *io_read;
|
IOHandler *io_read;
|
||||||
IOHandler *io_write;
|
IOHandler *io_write;
|
||||||
int deleted;
|
int deleted;
|
||||||
int pollfds_idx;
|
|
||||||
void *opaque;
|
void *opaque;
|
||||||
QLIST_ENTRY(AioHandler) node;
|
QLIST_ENTRY(AioHandler) node;
|
||||||
};
|
};
|
||||||
|
@ -83,7 +82,6 @@ void aio_set_fd_handler(AioContext *ctx,
|
||||||
node->io_read = io_read;
|
node->io_read = io_read;
|
||||||
node->io_write = io_write;
|
node->io_write = io_write;
|
||||||
node->opaque = opaque;
|
node->opaque = opaque;
|
||||||
node->pollfds_idx = -1;
|
|
||||||
|
|
||||||
node->pfd.events = (io_read ? G_IO_IN | G_IO_HUP | G_IO_ERR : 0);
|
node->pfd.events = (io_read ? G_IO_IN | G_IO_HUP | G_IO_ERR : 0);
|
||||||
node->pfd.events |= (io_write ? G_IO_OUT | G_IO_ERR : 0);
|
node->pfd.events |= (io_write ? G_IO_OUT | G_IO_ERR : 0);
|
||||||
|
@ -186,13 +184,61 @@ bool aio_dispatch(AioContext *ctx)
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* These thread-local variables are used only in a small part of aio_poll
|
||||||
|
* around the call to the poll() system call. In particular they are not
|
||||||
|
* used while aio_poll is performing callbacks, which makes it much easier
|
||||||
|
* to think about reentrancy!
|
||||||
|
*
|
||||||
|
* Stack-allocated arrays would be perfect but they have size limitations;
|
||||||
|
* heap allocation is expensive enough that we want to reuse arrays across
|
||||||
|
* calls to aio_poll(). And because poll() has to be called without holding
|
||||||
|
* any lock, the arrays cannot be stored in AioContext. Thread-local data
|
||||||
|
* has none of the disadvantages of these three options.
|
||||||
|
*/
|
||||||
|
static __thread GPollFD *pollfds;
|
||||||
|
static __thread AioHandler **nodes;
|
||||||
|
static __thread unsigned npfd, nalloc;
|
||||||
|
static __thread Notifier pollfds_cleanup_notifier;
|
||||||
|
|
||||||
|
static void pollfds_cleanup(Notifier *n, void *unused)
|
||||||
|
{
|
||||||
|
g_assert(npfd == 0);
|
||||||
|
g_free(pollfds);
|
||||||
|
g_free(nodes);
|
||||||
|
nalloc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_pollfd(AioHandler *node)
|
||||||
|
{
|
||||||
|
if (npfd == nalloc) {
|
||||||
|
if (nalloc == 0) {
|
||||||
|
pollfds_cleanup_notifier.notify = pollfds_cleanup;
|
||||||
|
qemu_thread_atexit_add(&pollfds_cleanup_notifier);
|
||||||
|
nalloc = 8;
|
||||||
|
} else {
|
||||||
|
g_assert(nalloc <= INT_MAX);
|
||||||
|
nalloc *= 2;
|
||||||
|
}
|
||||||
|
pollfds = g_renew(GPollFD, pollfds, nalloc);
|
||||||
|
nodes = g_renew(AioHandler *, nodes, nalloc);
|
||||||
|
}
|
||||||
|
nodes[npfd] = node;
|
||||||
|
pollfds[npfd] = (GPollFD) {
|
||||||
|
.fd = node->pfd.fd,
|
||||||
|
.events = node->pfd.events,
|
||||||
|
};
|
||||||
|
npfd++;
|
||||||
|
}
|
||||||
|
|
||||||
bool aio_poll(AioContext *ctx, bool blocking)
|
bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
{
|
{
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
bool was_dispatching;
|
bool was_dispatching;
|
||||||
int ret;
|
int i, ret;
|
||||||
bool progress;
|
bool progress;
|
||||||
|
int64_t timeout;
|
||||||
|
|
||||||
|
aio_context_acquire(ctx);
|
||||||
was_dispatching = ctx->dispatching;
|
was_dispatching = ctx->dispatching;
|
||||||
progress = false;
|
progress = false;
|
||||||
|
|
||||||
|
@ -210,39 +256,36 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
|
|
||||||
ctx->walking_handlers++;
|
ctx->walking_handlers++;
|
||||||
|
|
||||||
g_array_set_size(ctx->pollfds, 0);
|
assert(npfd == 0);
|
||||||
|
|
||||||
/* fill pollfds */
|
/* fill pollfds */
|
||||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
node->pollfds_idx = -1;
|
|
||||||
if (!node->deleted && node->pfd.events) {
|
if (!node->deleted && node->pfd.events) {
|
||||||
GPollFD pfd = {
|
add_pollfd(node);
|
||||||
.fd = node->pfd.fd,
|
|
||||||
.events = node->pfd.events,
|
|
||||||
};
|
|
||||||
node->pollfds_idx = ctx->pollfds->len;
|
|
||||||
g_array_append_val(ctx->pollfds, pfd);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->walking_handlers--;
|
timeout = blocking ? aio_compute_timeout(ctx) : 0;
|
||||||
|
|
||||||
/* wait until next event */
|
/* wait until next event */
|
||||||
ret = qemu_poll_ns((GPollFD *)ctx->pollfds->data,
|
if (timeout) {
|
||||||
ctx->pollfds->len,
|
aio_context_release(ctx);
|
||||||
blocking ? aio_compute_timeout(ctx) : 0);
|
}
|
||||||
|
ret = qemu_poll_ns((GPollFD *)pollfds, npfd, timeout);
|
||||||
|
if (timeout) {
|
||||||
|
aio_context_acquire(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
/* if we have any readable fds, dispatch event */
|
/* if we have any readable fds, dispatch event */
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
for (i = 0; i < npfd; i++) {
|
||||||
if (node->pollfds_idx != -1) {
|
nodes[i]->pfd.revents = pollfds[i].revents;
|
||||||
GPollFD *pfd = &g_array_index(ctx->pollfds, GPollFD,
|
|
||||||
node->pollfds_idx);
|
|
||||||
node->pfd.revents = pfd->revents;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
npfd = 0;
|
||||||
|
ctx->walking_handlers--;
|
||||||
|
|
||||||
/* Run dispatch even if there were no readable fds to run timers */
|
/* Run dispatch even if there were no readable fds to run timers */
|
||||||
aio_set_dispatching(ctx, true);
|
aio_set_dispatching(ctx, true);
|
||||||
if (aio_dispatch(ctx)) {
|
if (aio_dispatch(ctx)) {
|
||||||
|
@ -250,5 +293,7 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
}
|
}
|
||||||
|
|
||||||
aio_set_dispatching(ctx, was_dispatching);
|
aio_set_dispatching(ctx, was_dispatching);
|
||||||
|
aio_context_release(ctx);
|
||||||
|
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,6 +283,7 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
int count;
|
int count;
|
||||||
int timeout;
|
int timeout;
|
||||||
|
|
||||||
|
aio_context_acquire(ctx);
|
||||||
have_select_revents = aio_prepare(ctx);
|
have_select_revents = aio_prepare(ctx);
|
||||||
if (have_select_revents) {
|
if (have_select_revents) {
|
||||||
blocking = false;
|
blocking = false;
|
||||||
|
@ -323,7 +324,13 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
|
|
||||||
timeout = blocking
|
timeout = blocking
|
||||||
? qemu_timeout_ns_to_ms(aio_compute_timeout(ctx)) : 0;
|
? qemu_timeout_ns_to_ms(aio_compute_timeout(ctx)) : 0;
|
||||||
|
if (timeout) {
|
||||||
|
aio_context_release(ctx);
|
||||||
|
}
|
||||||
ret = WaitForMultipleObjects(count, events, FALSE, timeout);
|
ret = WaitForMultipleObjects(count, events, FALSE, timeout);
|
||||||
|
if (timeout) {
|
||||||
|
aio_context_acquire(ctx);
|
||||||
|
}
|
||||||
aio_set_dispatching(ctx, true);
|
aio_set_dispatching(ctx, true);
|
||||||
|
|
||||||
if (first && aio_bh_poll(ctx)) {
|
if (first && aio_bh_poll(ctx)) {
|
||||||
|
@ -349,5 +356,6 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||||
|
|
||||||
aio_set_dispatching(ctx, was_dispatching);
|
aio_set_dispatching(ctx, was_dispatching);
|
||||||
|
aio_context_release(ctx);
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
10
async.c
10
async.c
|
@ -230,7 +230,6 @@ aio_ctx_finalize(GSource *source)
|
||||||
event_notifier_cleanup(&ctx->notifier);
|
event_notifier_cleanup(&ctx->notifier);
|
||||||
rfifolock_destroy(&ctx->lock);
|
rfifolock_destroy(&ctx->lock);
|
||||||
qemu_mutex_destroy(&ctx->bh_lock);
|
qemu_mutex_destroy(&ctx->bh_lock);
|
||||||
g_array_free(ctx->pollfds, TRUE);
|
|
||||||
timerlistgroup_deinit(&ctx->tlg);
|
timerlistgroup_deinit(&ctx->tlg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,12 +280,6 @@ static void aio_timerlist_notify(void *opaque)
|
||||||
aio_notify(opaque);
|
aio_notify(opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void aio_rfifolock_cb(void *opaque)
|
|
||||||
{
|
|
||||||
/* Kick owner thread in case they are blocked in aio_poll() */
|
|
||||||
aio_notify(opaque);
|
|
||||||
}
|
|
||||||
|
|
||||||
AioContext *aio_context_new(Error **errp)
|
AioContext *aio_context_new(Error **errp)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -302,10 +295,9 @@ AioContext *aio_context_new(Error **errp)
|
||||||
aio_set_event_notifier(ctx, &ctx->notifier,
|
aio_set_event_notifier(ctx, &ctx->notifier,
|
||||||
(EventNotifierHandler *)
|
(EventNotifierHandler *)
|
||||||
event_notifier_test_and_clear);
|
event_notifier_test_and_clear);
|
||||||
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, NULL, NULL);
|
||||||
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
|
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
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 += raw_bsd.o qcow.o vdi.o vmdk.o cloop.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,7 +9,7 @@ block-obj-y += block-backend.o 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 mirror.o
|
block-obj-y += null.o mirror.o io.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
|
||||||
|
@ -37,6 +37,7 @@ gluster.o-libs := $(GLUSTERFS_LIBS)
|
||||||
ssh.o-cflags := $(LIBSSH2_CFLAGS)
|
ssh.o-cflags := $(LIBSSH2_CFLAGS)
|
||||||
ssh.o-libs := $(LIBSSH2_LIBS)
|
ssh.o-libs := $(LIBSSH2_LIBS)
|
||||||
archipelago.o-libs := $(ARCHIPELAGO_LIBS)
|
archipelago.o-libs := $(ARCHIPELAGO_LIBS)
|
||||||
|
block-obj-m += dmg.o
|
||||||
dmg.o-libs := $(BZIP2_LIBS)
|
dmg.o-libs := $(BZIP2_LIBS)
|
||||||
qcow.o-libs := -lz
|
qcow.o-libs := -lz
|
||||||
linux-aio.o-libs := -laio
|
linux-aio.o-libs := -laio
|
||||||
|
|
155
block/backup.c
155
block/backup.c
|
@ -37,6 +37,8 @@ typedef struct CowRequest {
|
||||||
typedef struct BackupBlockJob {
|
typedef struct BackupBlockJob {
|
||||||
BlockJob common;
|
BlockJob common;
|
||||||
BlockDriverState *target;
|
BlockDriverState *target;
|
||||||
|
/* bitmap for sync=dirty-bitmap */
|
||||||
|
BdrvDirtyBitmap *sync_bitmap;
|
||||||
MirrorSyncMode sync_mode;
|
MirrorSyncMode sync_mode;
|
||||||
RateLimit limit;
|
RateLimit limit;
|
||||||
BlockdevOnError on_source_error;
|
BlockdevOnError on_source_error;
|
||||||
|
@ -242,6 +244,91 @@ static void backup_complete(BlockJob *job, void *opaque)
|
||||||
g_free(data);
|
g_free(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool coroutine_fn yield_and_check(BackupBlockJob *job)
|
||||||
|
{
|
||||||
|
if (block_job_is_cancelled(&job->common)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we need to yield so that bdrv_drain_all() returns.
|
||||||
|
* (without, VM does not reboot)
|
||||||
|
*/
|
||||||
|
if (job->common.speed) {
|
||||||
|
uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
|
||||||
|
job->sectors_read);
|
||||||
|
job->sectors_read = 0;
|
||||||
|
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
|
||||||
|
} else {
|
||||||
|
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block_job_is_cancelled(&job->common)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coroutine_fn backup_run_incremental(BackupBlockJob *job)
|
||||||
|
{
|
||||||
|
bool error_is_read;
|
||||||
|
int ret = 0;
|
||||||
|
int clusters_per_iter;
|
||||||
|
uint32_t granularity;
|
||||||
|
int64_t sector;
|
||||||
|
int64_t cluster;
|
||||||
|
int64_t end;
|
||||||
|
int64_t last_cluster = -1;
|
||||||
|
BlockDriverState *bs = job->common.bs;
|
||||||
|
HBitmapIter hbi;
|
||||||
|
|
||||||
|
granularity = bdrv_dirty_bitmap_granularity(job->sync_bitmap);
|
||||||
|
clusters_per_iter = MAX((granularity / BACKUP_CLUSTER_SIZE), 1);
|
||||||
|
bdrv_dirty_iter_init(job->sync_bitmap, &hbi);
|
||||||
|
|
||||||
|
/* Find the next dirty sector(s) */
|
||||||
|
while ((sector = hbitmap_iter_next(&hbi)) != -1) {
|
||||||
|
cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
|
||||||
|
|
||||||
|
/* Fake progress updates for any clusters we skipped */
|
||||||
|
if (cluster != last_cluster + 1) {
|
||||||
|
job->common.offset += ((cluster - last_cluster - 1) *
|
||||||
|
BACKUP_CLUSTER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (end = cluster + clusters_per_iter; cluster < end; cluster++) {
|
||||||
|
do {
|
||||||
|
if (yield_and_check(job)) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = backup_do_cow(bs, cluster * BACKUP_SECTORS_PER_CLUSTER,
|
||||||
|
BACKUP_SECTORS_PER_CLUSTER, &error_is_read);
|
||||||
|
if ((ret < 0) &&
|
||||||
|
backup_error_action(job, error_is_read, -ret) ==
|
||||||
|
BLOCK_ERROR_ACTION_REPORT) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} while (ret < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the bitmap granularity is smaller than the backup granularity,
|
||||||
|
* we need to advance the iterator pointer to the next cluster. */
|
||||||
|
if (granularity < BACKUP_CLUSTER_SIZE) {
|
||||||
|
bdrv_set_dirty_iter(&hbi, cluster * BACKUP_SECTORS_PER_CLUSTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_cluster = cluster - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Play some final catchup with the progress meter */
|
||||||
|
end = DIV_ROUND_UP(job->common.len, BACKUP_CLUSTER_SIZE);
|
||||||
|
if (last_cluster + 1 < end) {
|
||||||
|
job->common.offset += ((end - last_cluster - 1) * BACKUP_CLUSTER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static void coroutine_fn backup_run(void *opaque)
|
static void coroutine_fn backup_run(void *opaque)
|
||||||
{
|
{
|
||||||
BackupBlockJob *job = opaque;
|
BackupBlockJob *job = opaque;
|
||||||
|
@ -259,8 +346,7 @@ static void coroutine_fn backup_run(void *opaque)
|
||||||
qemu_co_rwlock_init(&job->flush_rwlock);
|
qemu_co_rwlock_init(&job->flush_rwlock);
|
||||||
|
|
||||||
start = 0;
|
start = 0;
|
||||||
end = DIV_ROUND_UP(job->common.len / BDRV_SECTOR_SIZE,
|
end = DIV_ROUND_UP(job->common.len, BACKUP_CLUSTER_SIZE);
|
||||||
BACKUP_SECTORS_PER_CLUSTER);
|
|
||||||
|
|
||||||
job->bitmap = hbitmap_alloc(end, 0);
|
job->bitmap = hbitmap_alloc(end, 0);
|
||||||
|
|
||||||
|
@ -278,28 +364,13 @@ static void coroutine_fn backup_run(void *opaque)
|
||||||
qemu_coroutine_yield();
|
qemu_coroutine_yield();
|
||||||
job->common.busy = true;
|
job->common.busy = true;
|
||||||
}
|
}
|
||||||
|
} else if (job->sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
|
||||||
|
ret = backup_run_incremental(job);
|
||||||
} else {
|
} else {
|
||||||
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
||||||
for (; start < end; start++) {
|
for (; start < end; start++) {
|
||||||
bool error_is_read;
|
bool error_is_read;
|
||||||
|
if (yield_and_check(job)) {
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* we need to yield so that qemu_aio_flush() returns.
|
|
||||||
* (without, VM does not reboot)
|
|
||||||
*/
|
|
||||||
if (job->common.speed) {
|
|
||||||
uint64_t delay_ns = ratelimit_calculate_delay(
|
|
||||||
&job->limit, job->sectors_read);
|
|
||||||
job->sectors_read = 0;
|
|
||||||
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
|
|
||||||
} else {
|
|
||||||
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,6 +428,18 @@ static void coroutine_fn backup_run(void *opaque)
|
||||||
qemu_co_rwlock_wrlock(&job->flush_rwlock);
|
qemu_co_rwlock_wrlock(&job->flush_rwlock);
|
||||||
qemu_co_rwlock_unlock(&job->flush_rwlock);
|
qemu_co_rwlock_unlock(&job->flush_rwlock);
|
||||||
|
|
||||||
|
if (job->sync_bitmap) {
|
||||||
|
BdrvDirtyBitmap *bm;
|
||||||
|
if (ret < 0) {
|
||||||
|
/* Merge the successor back into the parent, delete nothing. */
|
||||||
|
bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
|
||||||
|
assert(bm);
|
||||||
|
} else {
|
||||||
|
/* Everything is fine, delete this bitmap and install the backup. */
|
||||||
|
bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
|
||||||
|
assert(bm);
|
||||||
|
}
|
||||||
|
}
|
||||||
hbitmap_free(job->bitmap);
|
hbitmap_free(job->bitmap);
|
||||||
|
|
||||||
bdrv_iostatus_disable(target);
|
bdrv_iostatus_disable(target);
|
||||||
|
@ -369,6 +452,7 @@ static void coroutine_fn backup_run(void *opaque)
|
||||||
|
|
||||||
void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
int64_t speed, MirrorSyncMode sync_mode,
|
int64_t speed, MirrorSyncMode sync_mode,
|
||||||
|
BdrvDirtyBitmap *sync_bitmap,
|
||||||
BlockdevOnError on_source_error,
|
BlockdevOnError on_source_error,
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
BlockCompletionFunc *cb, void *opaque,
|
BlockCompletionFunc *cb, void *opaque,
|
||||||
|
@ -412,17 +496,36 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
|
||||||
|
if (!sync_bitmap) {
|
||||||
|
error_setg(errp, "must provide a valid bitmap name for "
|
||||||
|
"\"dirty-bitmap\" sync mode");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a new bitmap, and freeze/disable this one. */
|
||||||
|
if (bdrv_dirty_bitmap_create_successor(bs, sync_bitmap, errp) < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (sync_bitmap) {
|
||||||
|
error_setg(errp,
|
||||||
|
"a sync_bitmap was provided to backup_run, "
|
||||||
|
"but received an incompatible sync_mode (%s)",
|
||||||
|
MirrorSyncMode_lookup[sync_mode]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
len = bdrv_getlength(bs);
|
len = bdrv_getlength(bs);
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
error_setg_errno(errp, -len, "unable to get length for '%s'",
|
error_setg_errno(errp, -len, "unable to get length for '%s'",
|
||||||
bdrv_get_device_name(bs));
|
bdrv_get_device_name(bs));
|
||||||
return;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackupBlockJob *job = block_job_create(&backup_job_driver, bs, speed,
|
BackupBlockJob *job = block_job_create(&backup_job_driver, bs, speed,
|
||||||
cb, opaque, errp);
|
cb, opaque, errp);
|
||||||
if (!job) {
|
if (!job) {
|
||||||
return;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_op_block_all(target, job->common.blocker);
|
bdrv_op_block_all(target, job->common.blocker);
|
||||||
|
@ -431,7 +534,15 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
job->on_target_error = on_target_error;
|
job->on_target_error = on_target_error;
|
||||||
job->target = target;
|
job->target = target;
|
||||||
job->sync_mode = sync_mode;
|
job->sync_mode = sync_mode;
|
||||||
|
job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP ?
|
||||||
|
sync_bitmap : NULL;
|
||||||
job->common.len = len;
|
job->common.len = len;
|
||||||
job->common.co = qemu_coroutine_create(backup_run);
|
job->common.co = qemu_coroutine_create(backup_run);
|
||||||
qemu_coroutine_enter(job->common.co, job);
|
qemu_coroutine_enter(job->common.co, job);
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (sync_bitmap) {
|
||||||
|
bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -721,6 +721,11 @@ static int64_t blkdebug_getlength(BlockDriverState *bs)
|
||||||
return bdrv_getlength(bs->file);
|
return bdrv_getlength(bs->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int blkdebug_truncate(BlockDriverState *bs, int64_t offset)
|
||||||
|
{
|
||||||
|
return bdrv_truncate(bs->file, offset);
|
||||||
|
}
|
||||||
|
|
||||||
static void blkdebug_refresh_filename(BlockDriverState *bs)
|
static void blkdebug_refresh_filename(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
QDict *opts;
|
QDict *opts;
|
||||||
|
@ -779,6 +784,7 @@ static BlockDriver bdrv_blkdebug = {
|
||||||
.bdrv_file_open = blkdebug_open,
|
.bdrv_file_open = blkdebug_open,
|
||||||
.bdrv_close = blkdebug_close,
|
.bdrv_close = blkdebug_close,
|
||||||
.bdrv_getlength = blkdebug_getlength,
|
.bdrv_getlength = blkdebug_getlength,
|
||||||
|
.bdrv_truncate = blkdebug_truncate,
|
||||||
.bdrv_refresh_filename = blkdebug_refresh_filename,
|
.bdrv_refresh_filename = blkdebug_refresh_filename,
|
||||||
|
|
||||||
.bdrv_aio_readv = blkdebug_aio_readv,
|
.bdrv_aio_readv = blkdebug_aio_readv,
|
||||||
|
|
|
@ -515,6 +515,17 @@ int blk_write(BlockBackend *blk, int64_t sector_num, const uint8_t *buf,
|
||||||
return bdrv_write(blk->bs, sector_num, buf, nb_sectors);
|
return bdrv_write(blk->bs, sector_num, buf, nb_sectors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int blk_write_zeroes(BlockBackend *blk, int64_t sector_num,
|
||||||
|
int nb_sectors, BdrvRequestFlags flags)
|
||||||
|
{
|
||||||
|
int ret = blk_check_request(blk, sector_num, nb_sectors);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bdrv_write_zeroes(blk->bs, sector_num, nb_sectors, flags);
|
||||||
|
}
|
||||||
|
|
||||||
static void error_callback_bh(void *opaque)
|
static void error_callback_bh(void *opaque)
|
||||||
{
|
{
|
||||||
struct BlockBackendAIOCB *acb = opaque;
|
struct BlockBackendAIOCB *acb = opaque;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,7 @@
|
||||||
* QEMU Block driver for iSCSI images
|
* QEMU Block driver for iSCSI images
|
||||||
*
|
*
|
||||||
* Copyright (c) 2010-2011 Ronnie Sahlberg <ronniesahlberg@gmail.com>
|
* Copyright (c) 2010-2011 Ronnie Sahlberg <ronniesahlberg@gmail.com>
|
||||||
* Copyright (c) 2012-2014 Peter Lieven <pl@kamp.de>
|
* Copyright (c) 2012-2015 Peter Lieven <pl@kamp.de>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -57,9 +57,6 @@ typedef struct IscsiLun {
|
||||||
int events;
|
int events;
|
||||||
QEMUTimer *nop_timer;
|
QEMUTimer *nop_timer;
|
||||||
QEMUTimer *event_timer;
|
QEMUTimer *event_timer;
|
||||||
uint8_t lbpme;
|
|
||||||
uint8_t lbprz;
|
|
||||||
uint8_t has_write_same;
|
|
||||||
struct scsi_inquiry_logical_block_provisioning lbp;
|
struct scsi_inquiry_logical_block_provisioning lbp;
|
||||||
struct scsi_inquiry_block_limits bl;
|
struct scsi_inquiry_block_limits bl;
|
||||||
unsigned char *zeroblock;
|
unsigned char *zeroblock;
|
||||||
|
@ -67,6 +64,11 @@ typedef struct IscsiLun {
|
||||||
int cluster_sectors;
|
int cluster_sectors;
|
||||||
bool use_16_for_rw;
|
bool use_16_for_rw;
|
||||||
bool write_protected;
|
bool write_protected;
|
||||||
|
bool lbpme;
|
||||||
|
bool lbprz;
|
||||||
|
bool dpofua;
|
||||||
|
bool has_write_same;
|
||||||
|
bool force_next_flush;
|
||||||
} IscsiLun;
|
} IscsiLun;
|
||||||
|
|
||||||
typedef struct IscsiTask {
|
typedef struct IscsiTask {
|
||||||
|
@ -79,6 +81,7 @@ typedef struct IscsiTask {
|
||||||
QEMUBH *bh;
|
QEMUBH *bh;
|
||||||
IscsiLun *iscsilun;
|
IscsiLun *iscsilun;
|
||||||
QEMUTimer retry_timer;
|
QEMUTimer retry_timer;
|
||||||
|
bool force_next_flush;
|
||||||
} IscsiTask;
|
} IscsiTask;
|
||||||
|
|
||||||
typedef struct IscsiAIOCB {
|
typedef struct IscsiAIOCB {
|
||||||
|
@ -100,7 +103,7 @@ typedef struct IscsiAIOCB {
|
||||||
#define NOP_INTERVAL 5000
|
#define NOP_INTERVAL 5000
|
||||||
#define MAX_NOP_FAILURES 3
|
#define MAX_NOP_FAILURES 3
|
||||||
#define ISCSI_CMD_RETRIES ARRAY_SIZE(iscsi_retry_times)
|
#define ISCSI_CMD_RETRIES ARRAY_SIZE(iscsi_retry_times)
|
||||||
static const unsigned iscsi_retry_times[] = {8, 32, 128, 512, 2048};
|
static const unsigned iscsi_retry_times[] = {8, 32, 128, 512, 2048, 8192, 32768};
|
||||||
|
|
||||||
/* this threshold is a trade-off knob to choose between
|
/* this threshold is a trade-off knob to choose between
|
||||||
* the potential additional overhead of an extra GET_LBA_STATUS request
|
* the potential additional overhead of an extra GET_LBA_STATUS request
|
||||||
|
@ -183,10 +186,13 @@ iscsi_co_generic_cb(struct iscsi_context *iscsi, int status,
|
||||||
iTask->do_retry = 1;
|
iTask->do_retry = 1;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if (status == SCSI_STATUS_BUSY) {
|
/* status 0x28 is SCSI_TASK_SET_FULL. It was first introduced
|
||||||
|
* in libiscsi 1.10.0. Hardcode this value here to avoid
|
||||||
|
* the need to bump the libiscsi requirement to 1.10.0 */
|
||||||
|
if (status == SCSI_STATUS_BUSY || status == 0x28) {
|
||||||
unsigned retry_time =
|
unsigned retry_time =
|
||||||
exp_random(iscsi_retry_times[iTask->retries - 1]);
|
exp_random(iscsi_retry_times[iTask->retries - 1]);
|
||||||
error_report("iSCSI Busy (retry #%u in %u ms): %s",
|
error_report("iSCSI Busy/TaskSetFull (retry #%u in %u ms): %s",
|
||||||
iTask->retries, retry_time,
|
iTask->retries, retry_time,
|
||||||
iscsi_get_error(iscsi));
|
iscsi_get_error(iscsi));
|
||||||
aio_timer_init(iTask->iscsilun->aio_context,
|
aio_timer_init(iTask->iscsilun->aio_context,
|
||||||
|
@ -199,6 +205,8 @@ iscsi_co_generic_cb(struct iscsi_context *iscsi, int status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
error_report("iSCSI Failure: %s", iscsi_get_error(iscsi));
|
error_report("iSCSI Failure: %s", iscsi_get_error(iscsi));
|
||||||
|
} else {
|
||||||
|
iTask->iscsilun->force_next_flush |= iTask->force_next_flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
@ -369,6 +377,7 @@ static int coroutine_fn iscsi_co_writev(BlockDriverState *bs,
|
||||||
struct IscsiTask iTask;
|
struct IscsiTask iTask;
|
||||||
uint64_t lba;
|
uint64_t lba;
|
||||||
uint32_t num_sectors;
|
uint32_t num_sectors;
|
||||||
|
int fua;
|
||||||
|
|
||||||
if (!is_request_lun_aligned(sector_num, nb_sectors, iscsilun)) {
|
if (!is_request_lun_aligned(sector_num, nb_sectors, iscsilun)) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
@ -384,15 +393,17 @@ static int coroutine_fn iscsi_co_writev(BlockDriverState *bs,
|
||||||
num_sectors = sector_qemu2lun(nb_sectors, iscsilun);
|
num_sectors = sector_qemu2lun(nb_sectors, iscsilun);
|
||||||
iscsi_co_init_iscsitask(iscsilun, &iTask);
|
iscsi_co_init_iscsitask(iscsilun, &iTask);
|
||||||
retry:
|
retry:
|
||||||
|
fua = iscsilun->dpofua && !bs->enable_write_cache;
|
||||||
|
iTask.force_next_flush = !fua;
|
||||||
if (iscsilun->use_16_for_rw) {
|
if (iscsilun->use_16_for_rw) {
|
||||||
iTask.task = iscsi_write16_task(iscsilun->iscsi, iscsilun->lun, lba,
|
iTask.task = iscsi_write16_task(iscsilun->iscsi, iscsilun->lun, lba,
|
||||||
NULL, num_sectors * iscsilun->block_size,
|
NULL, num_sectors * iscsilun->block_size,
|
||||||
iscsilun->block_size, 0, 0, 0, 0, 0,
|
iscsilun->block_size, 0, 0, fua, 0, 0,
|
||||||
iscsi_co_generic_cb, &iTask);
|
iscsi_co_generic_cb, &iTask);
|
||||||
} else {
|
} else {
|
||||||
iTask.task = iscsi_write10_task(iscsilun->iscsi, iscsilun->lun, lba,
|
iTask.task = iscsi_write10_task(iscsilun->iscsi, iscsilun->lun, lba,
|
||||||
NULL, num_sectors * iscsilun->block_size,
|
NULL, num_sectors * iscsilun->block_size,
|
||||||
iscsilun->block_size, 0, 0, 0, 0, 0,
|
iscsilun->block_size, 0, 0, fua, 0, 0,
|
||||||
iscsi_co_generic_cb, &iTask);
|
iscsi_co_generic_cb, &iTask);
|
||||||
}
|
}
|
||||||
if (iTask.task == NULL) {
|
if (iTask.task == NULL) {
|
||||||
|
@ -460,7 +471,7 @@ static int64_t coroutine_fn iscsi_co_get_block_status(BlockDriverState *bs,
|
||||||
*pnum = nb_sectors;
|
*pnum = nb_sectors;
|
||||||
|
|
||||||
/* LUN does not support logical block provisioning */
|
/* LUN does not support logical block provisioning */
|
||||||
if (iscsilun->lbpme == 0) {
|
if (!iscsilun->lbpme) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,8 +631,12 @@ static int coroutine_fn iscsi_co_flush(BlockDriverState *bs)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
iscsi_co_init_iscsitask(iscsilun, &iTask);
|
if (!iscsilun->force_next_flush) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
iscsilun->force_next_flush = false;
|
||||||
|
|
||||||
|
iscsi_co_init_iscsitask(iscsilun, &iTask);
|
||||||
retry:
|
retry:
|
||||||
if (iscsi_synchronizecache10_task(iscsilun->iscsi, iscsilun->lun, 0, 0, 0,
|
if (iscsi_synchronizecache10_task(iscsilun->iscsi, iscsilun->lun, 0, 0, 0,
|
||||||
0, iscsi_co_generic_cb, &iTask) == NULL) {
|
0, iscsi_co_generic_cb, &iTask) == NULL) {
|
||||||
|
@ -917,6 +932,7 @@ coroutine_fn iscsi_co_write_zeroes(BlockDriverState *bs, int64_t sector_num,
|
||||||
}
|
}
|
||||||
|
|
||||||
iscsi_co_init_iscsitask(iscsilun, &iTask);
|
iscsi_co_init_iscsitask(iscsilun, &iTask);
|
||||||
|
iTask.force_next_flush = true;
|
||||||
retry:
|
retry:
|
||||||
if (use_16_for_ws) {
|
if (use_16_for_ws) {
|
||||||
iTask.task = iscsi_writesame16_task(iscsilun->iscsi, iscsilun->lun, lba,
|
iTask.task = iscsi_writesame16_task(iscsilun->iscsi, iscsilun->lun, lba,
|
||||||
|
@ -1121,8 +1137,8 @@ static void iscsi_readcapacity_sync(IscsiLun *iscsilun, Error **errp)
|
||||||
} else {
|
} else {
|
||||||
iscsilun->block_size = rc16->block_length;
|
iscsilun->block_size = rc16->block_length;
|
||||||
iscsilun->num_blocks = rc16->returned_lba + 1;
|
iscsilun->num_blocks = rc16->returned_lba + 1;
|
||||||
iscsilun->lbpme = rc16->lbpme;
|
iscsilun->lbpme = !!rc16->lbpme;
|
||||||
iscsilun->lbprz = rc16->lbprz;
|
iscsilun->lbprz = !!rc16->lbprz;
|
||||||
iscsilun->use_16_for_rw = (rc16->returned_lba > 0xffffffff);
|
iscsilun->use_16_for_rw = (rc16->returned_lba > 0xffffffff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1253,11 +1269,12 @@ static void iscsi_attach_aio_context(BlockDriverState *bs,
|
||||||
iscsi_timed_set_events, iscsilun);
|
iscsi_timed_set_events, iscsilun);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool iscsi_is_write_protected(IscsiLun *iscsilun)
|
static void iscsi_modesense_sync(IscsiLun *iscsilun)
|
||||||
{
|
{
|
||||||
struct scsi_task *task;
|
struct scsi_task *task;
|
||||||
struct scsi_mode_sense *ms = NULL;
|
struct scsi_mode_sense *ms = NULL;
|
||||||
bool wrprotected = false;
|
iscsilun->write_protected = false;
|
||||||
|
iscsilun->dpofua = false;
|
||||||
|
|
||||||
task = iscsi_modesense6_sync(iscsilun->iscsi, iscsilun->lun,
|
task = iscsi_modesense6_sync(iscsilun->iscsi, iscsilun->lun,
|
||||||
1, SCSI_MODESENSE_PC_CURRENT,
|
1, SCSI_MODESENSE_PC_CURRENT,
|
||||||
|
@ -1278,13 +1295,13 @@ static bool iscsi_is_write_protected(IscsiLun *iscsilun)
|
||||||
iscsi_get_error(iscsilun->iscsi));
|
iscsi_get_error(iscsilun->iscsi));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
wrprotected = ms->device_specific_parameter & 0x80;
|
iscsilun->write_protected = ms->device_specific_parameter & 0x80;
|
||||||
|
iscsilun->dpofua = ms->device_specific_parameter & 0x10;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (task) {
|
if (task) {
|
||||||
scsi_free_scsi_task(task);
|
scsi_free_scsi_task(task);
|
||||||
}
|
}
|
||||||
return wrprotected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1403,7 +1420,8 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
scsi_free_scsi_task(task);
|
scsi_free_scsi_task(task);
|
||||||
task = NULL;
|
task = NULL;
|
||||||
|
|
||||||
iscsilun->write_protected = iscsi_is_write_protected(iscsilun);
|
iscsi_modesense_sync(iscsilun);
|
||||||
|
|
||||||
/* Check the write protect flag of the LUN if we want to write */
|
/* Check the write protect flag of the LUN if we want to write */
|
||||||
if (iscsilun->type == TYPE_DISK && (flags & BDRV_O_RDWR) &&
|
if (iscsilun->type == TYPE_DISK && (flags & BDRV_O_RDWR) &&
|
||||||
iscsilun->write_protected) {
|
iscsilun->write_protected) {
|
||||||
|
@ -1481,7 +1499,7 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
iscsilun->bl.opt_unmap_gran * iscsilun->block_size <= 16 * 1024 * 1024) {
|
iscsilun->bl.opt_unmap_gran * iscsilun->block_size <= 16 * 1024 * 1024) {
|
||||||
iscsilun->cluster_sectors = (iscsilun->bl.opt_unmap_gran *
|
iscsilun->cluster_sectors = (iscsilun->bl.opt_unmap_gran *
|
||||||
iscsilun->block_size) >> BDRV_SECTOR_BITS;
|
iscsilun->block_size) >> BDRV_SECTOR_BITS;
|
||||||
if (iscsilun->lbprz && !(bs->open_flags & BDRV_O_NOCACHE)) {
|
if (iscsilun->lbprz) {
|
||||||
iscsilun->allocationmap = iscsi_allocationmap_init(iscsilun);
|
iscsilun->allocationmap = iscsi_allocationmap_init(iscsilun);
|
||||||
if (iscsilun->allocationmap == NULL) {
|
if (iscsilun->allocationmap == NULL) {
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
|
@ -1501,6 +1519,9 @@ out:
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
if (iscsi != NULL) {
|
if (iscsi != NULL) {
|
||||||
|
if (iscsi_is_logged_in(iscsi)) {
|
||||||
|
iscsi_logout_sync(iscsi);
|
||||||
|
}
|
||||||
iscsi_destroy_context(iscsi);
|
iscsi_destroy_context(iscsi);
|
||||||
}
|
}
|
||||||
memset(iscsilun, 0, sizeof(IscsiLun));
|
memset(iscsilun, 0, sizeof(IscsiLun));
|
||||||
|
@ -1514,6 +1535,9 @@ static void iscsi_close(BlockDriverState *bs)
|
||||||
struct iscsi_context *iscsi = iscsilun->iscsi;
|
struct iscsi_context *iscsi = iscsilun->iscsi;
|
||||||
|
|
||||||
iscsi_detach_aio_context(bs);
|
iscsi_detach_aio_context(bs);
|
||||||
|
if (iscsi_is_logged_in(iscsi)) {
|
||||||
|
iscsi_logout_sync(iscsi);
|
||||||
|
}
|
||||||
iscsi_destroy_context(iscsi);
|
iscsi_destroy_context(iscsi);
|
||||||
g_free(iscsilun->zeroblock);
|
g_free(iscsilun->zeroblock);
|
||||||
g_free(iscsilun->allocationmap);
|
g_free(iscsilun->allocationmap);
|
||||||
|
@ -1649,7 +1673,7 @@ out:
|
||||||
static int iscsi_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
|
static int iscsi_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
|
||||||
{
|
{
|
||||||
IscsiLun *iscsilun = bs->opaque;
|
IscsiLun *iscsilun = bs->opaque;
|
||||||
bdi->unallocated_blocks_are_zero = !!iscsilun->lbprz;
|
bdi->unallocated_blocks_are_zero = iscsilun->lbprz;
|
||||||
bdi->can_write_zeroes_with_unmap = iscsilun->lbprz && iscsilun->lbp.lbpws;
|
bdi->can_write_zeroes_with_unmap = iscsilun->lbprz && iscsilun->lbp.lbpws;
|
||||||
bdi->cluster_size = iscsilun->cluster_sectors * BDRV_SECTOR_SIZE;
|
bdi->cluster_size = iscsilun->cluster_sectors * BDRV_SECTOR_SIZE;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -125,11 +125,9 @@ static void mirror_write_complete(void *opaque, int ret)
|
||||||
MirrorOp *op = opaque;
|
MirrorOp *op = opaque;
|
||||||
MirrorBlockJob *s = op->s;
|
MirrorBlockJob *s = op->s;
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
BlockDriverState *source = s->common.bs;
|
|
||||||
BlockErrorAction action;
|
BlockErrorAction action;
|
||||||
|
|
||||||
bdrv_set_dirty_bitmap(source, s->dirty_bitmap, op->sector_num,
|
bdrv_set_dirty_bitmap(s->dirty_bitmap, op->sector_num, op->nb_sectors);
|
||||||
op->nb_sectors);
|
|
||||||
action = mirror_error_action(s, false, -ret);
|
action = mirror_error_action(s, false, -ret);
|
||||||
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
|
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
|
||||||
s->ret = ret;
|
s->ret = ret;
|
||||||
|
@ -143,11 +141,9 @@ static void mirror_read_complete(void *opaque, int ret)
|
||||||
MirrorOp *op = opaque;
|
MirrorOp *op = opaque;
|
||||||
MirrorBlockJob *s = op->s;
|
MirrorBlockJob *s = op->s;
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
BlockDriverState *source = s->common.bs;
|
|
||||||
BlockErrorAction action;
|
BlockErrorAction action;
|
||||||
|
|
||||||
bdrv_set_dirty_bitmap(source, s->dirty_bitmap, op->sector_num,
|
bdrv_set_dirty_bitmap(s->dirty_bitmap, op->sector_num, op->nb_sectors);
|
||||||
op->nb_sectors);
|
|
||||||
action = mirror_error_action(s, true, -ret);
|
action = mirror_error_action(s, true, -ret);
|
||||||
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
|
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
|
||||||
s->ret = ret;
|
s->ret = ret;
|
||||||
|
@ -170,10 +166,9 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
||||||
|
|
||||||
s->sector_num = hbitmap_iter_next(&s->hbi);
|
s->sector_num = hbitmap_iter_next(&s->hbi);
|
||||||
if (s->sector_num < 0) {
|
if (s->sector_num < 0) {
|
||||||
bdrv_dirty_iter_init(source, s->dirty_bitmap, &s->hbi);
|
bdrv_dirty_iter_init(s->dirty_bitmap, &s->hbi);
|
||||||
s->sector_num = hbitmap_iter_next(&s->hbi);
|
s->sector_num = hbitmap_iter_next(&s->hbi);
|
||||||
trace_mirror_restart_iter(s,
|
trace_mirror_restart_iter(s, bdrv_get_dirty_count(s->dirty_bitmap));
|
||||||
bdrv_get_dirty_count(source, s->dirty_bitmap));
|
|
||||||
assert(s->sector_num >= 0);
|
assert(s->sector_num >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,8 +283,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
||||||
next_sector += sectors_per_chunk;
|
next_sector += sectors_per_chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_reset_dirty_bitmap(source, s->dirty_bitmap, sector_num,
|
bdrv_reset_dirty_bitmap(s->dirty_bitmap, sector_num, nb_sectors);
|
||||||
nb_sectors);
|
|
||||||
|
|
||||||
/* Copy the dirty cluster. */
|
/* Copy the dirty cluster. */
|
||||||
s->in_flight++;
|
s->in_flight++;
|
||||||
|
@ -446,7 +440,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
||||||
|
|
||||||
assert(n > 0);
|
assert(n > 0);
|
||||||
if (ret == 1) {
|
if (ret == 1) {
|
||||||
bdrv_set_dirty_bitmap(bs, s->dirty_bitmap, sector_num, n);
|
bdrv_set_dirty_bitmap(s->dirty_bitmap, sector_num, n);
|
||||||
sector_num = next;
|
sector_num = next;
|
||||||
} else {
|
} else {
|
||||||
sector_num += n;
|
sector_num += n;
|
||||||
|
@ -454,7 +448,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_dirty_iter_init(bs, s->dirty_bitmap, &s->hbi);
|
bdrv_dirty_iter_init(s->dirty_bitmap, &s->hbi);
|
||||||
last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint64_t delay_ns = 0;
|
uint64_t delay_ns = 0;
|
||||||
|
@ -466,7 +460,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
||||||
goto immediate_exit;
|
goto immediate_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
|
cnt = bdrv_get_dirty_count(s->dirty_bitmap);
|
||||||
/* s->common.offset contains the number of bytes already processed so
|
/* s->common.offset contains the number of bytes already processed so
|
||||||
* far, cnt is the number of dirty sectors remaining and
|
* far, cnt is the number of dirty sectors remaining and
|
||||||
* s->sectors_in_flight is the number of sectors currently being
|
* s->sectors_in_flight is the number of sectors currently being
|
||||||
|
@ -475,7 +469,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
||||||
(cnt + s->sectors_in_flight) * BDRV_SECTOR_SIZE;
|
(cnt + s->sectors_in_flight) * BDRV_SECTOR_SIZE;
|
||||||
|
|
||||||
/* Note that even when no rate limit is applied we need to yield
|
/* Note that even when no rate limit is applied we need to yield
|
||||||
* periodically with no pending I/O so that qemu_aio_flush() returns.
|
* periodically with no pending I/O so that bdrv_drain_all() returns.
|
||||||
* We do so every SLICE_TIME nanoseconds, or when there is an error,
|
* We do so every SLICE_TIME nanoseconds, or when there is an error,
|
||||||
* or when the source is clean, whichever comes first.
|
* or when the source is clean, whichever comes first.
|
||||||
*/
|
*/
|
||||||
|
@ -488,9 +482,6 @@ static void coroutine_fn mirror_run(void *opaque)
|
||||||
continue;
|
continue;
|
||||||
} else if (cnt != 0) {
|
} else if (cnt != 0) {
|
||||||
delay_ns = mirror_iteration(s);
|
delay_ns = mirror_iteration(s);
|
||||||
if (delay_ns == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,7 +507,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
||||||
|
|
||||||
should_complete = s->should_complete ||
|
should_complete = s->should_complete ||
|
||||||
block_job_is_cancelled(&s->common);
|
block_job_is_cancelled(&s->common);
|
||||||
cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
|
cnt = bdrv_get_dirty_count(s->dirty_bitmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,7 +522,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
||||||
*/
|
*/
|
||||||
trace_mirror_before_drain(s, cnt);
|
trace_mirror_before_drain(s, cnt);
|
||||||
bdrv_drain(bs);
|
bdrv_drain(bs);
|
||||||
cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
|
cnt = bdrv_get_dirty_count(s->dirty_bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
@ -634,7 +625,7 @@ static void mirror_complete(BlockJob *job, Error **errp)
|
||||||
}
|
}
|
||||||
|
|
||||||
s->should_complete = true;
|
s->should_complete = true;
|
||||||
block_job_resume(job);
|
block_job_enter(&s->common);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const BlockJobDriver mirror_job_driver = {
|
static const BlockJobDriver mirror_job_driver = {
|
||||||
|
@ -656,7 +647,7 @@ static const BlockJobDriver commit_active_job_driver = {
|
||||||
|
|
||||||
static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
||||||
const char *replaces,
|
const char *replaces,
|
||||||
int64_t speed, int64_t granularity,
|
int64_t speed, uint32_t granularity,
|
||||||
int64_t buf_size,
|
int64_t buf_size,
|
||||||
BlockdevOnError on_source_error,
|
BlockdevOnError on_source_error,
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
|
@ -668,15 +659,7 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
||||||
MirrorBlockJob *s;
|
MirrorBlockJob *s;
|
||||||
|
|
||||||
if (granularity == 0) {
|
if (granularity == 0) {
|
||||||
/* Choose the default granularity based on the target file's cluster
|
granularity = bdrv_get_default_bitmap_granularity(target);
|
||||||
* size, clamped between 4k and 64k. */
|
|
||||||
BlockDriverInfo bdi;
|
|
||||||
if (bdrv_get_info(target, &bdi) >= 0 && bdi.cluster_size != 0) {
|
|
||||||
granularity = MAX(4096, bdi.cluster_size);
|
|
||||||
granularity = MIN(65536, granularity);
|
|
||||||
} else {
|
|
||||||
granularity = 65536;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert ((granularity & (granularity - 1)) == 0);
|
assert ((granularity & (granularity - 1)) == 0);
|
||||||
|
@ -703,7 +686,7 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
||||||
s->granularity = granularity;
|
s->granularity = granularity;
|
||||||
s->buf_size = MAX(buf_size, granularity);
|
s->buf_size = MAX(buf_size, granularity);
|
||||||
|
|
||||||
s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, errp);
|
s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp);
|
||||||
if (!s->dirty_bitmap) {
|
if (!s->dirty_bitmap) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -717,7 +700,7 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
|
||||||
|
|
||||||
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
const char *replaces,
|
const char *replaces,
|
||||||
int64_t speed, int64_t granularity, int64_t buf_size,
|
int64_t speed, uint32_t granularity, int64_t buf_size,
|
||||||
MirrorSyncMode mode, BlockdevOnError on_source_error,
|
MirrorSyncMode mode, BlockdevOnError on_source_error,
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
BlockCompletionFunc *cb,
|
BlockCompletionFunc *cb,
|
||||||
|
@ -726,6 +709,10 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
bool is_none_mode;
|
bool is_none_mode;
|
||||||
BlockDriverState *base;
|
BlockDriverState *base;
|
||||||
|
|
||||||
|
if (mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
|
||||||
|
error_setg(errp, "Sync mode 'dirty-bitmap' not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
|
is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
|
||||||
base = mode == MIRROR_SYNC_MODE_TOP ? bs->backing_hd : NULL;
|
base = mode == MIRROR_SYNC_MODE_TOP ? bs->backing_hd : NULL;
|
||||||
mirror_start_job(bs, target, replaces,
|
mirror_start_job(bs, target, replaces,
|
||||||
|
|
66
block/null.c
66
block/null.c
|
@ -12,8 +12,11 @@
|
||||||
|
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
|
|
||||||
|
#define NULL_OPT_LATENCY "latency-ns"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int64_t length;
|
int64_t length;
|
||||||
|
int64_t latency_ns;
|
||||||
} BDRVNullState;
|
} BDRVNullState;
|
||||||
|
|
||||||
static QemuOptsList runtime_opts = {
|
static QemuOptsList runtime_opts = {
|
||||||
|
@ -30,6 +33,12 @@ static QemuOptsList runtime_opts = {
|
||||||
.type = QEMU_OPT_SIZE,
|
.type = QEMU_OPT_SIZE,
|
||||||
.help = "size of the null block",
|
.help = "size of the null block",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = NULL_OPT_LATENCY,
|
||||||
|
.type = QEMU_OPT_NUMBER,
|
||||||
|
.help = "nanoseconds (approximated) to wait "
|
||||||
|
"before completing request",
|
||||||
|
},
|
||||||
{ /* end of list */ }
|
{ /* end of list */ }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -39,13 +48,20 @@ static int null_file_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
{
|
{
|
||||||
QemuOpts *opts;
|
QemuOpts *opts;
|
||||||
BDRVNullState *s = bs->opaque;
|
BDRVNullState *s = bs->opaque;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||||
qemu_opts_absorb_qdict(opts, options, &error_abort);
|
qemu_opts_absorb_qdict(opts, options, &error_abort);
|
||||||
s->length =
|
s->length =
|
||||||
qemu_opt_get_size(opts, BLOCK_OPT_SIZE, 1 << 30);
|
qemu_opt_get_size(opts, BLOCK_OPT_SIZE, 1 << 30);
|
||||||
|
s->latency_ns =
|
||||||
|
qemu_opt_get_number(opts, NULL_OPT_LATENCY, 0);
|
||||||
|
if (s->latency_ns < 0) {
|
||||||
|
error_setg(errp, "latency-ns is invalid");
|
||||||
|
ret = -EINVAL;
|
||||||
|
}
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void null_close(BlockDriverState *bs)
|
static void null_close(BlockDriverState *bs)
|
||||||
|
@ -58,28 +74,40 @@ static int64_t null_getlength(BlockDriverState *bs)
|
||||||
return s->length;
|
return s->length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static coroutine_fn int null_co_common(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
BDRVNullState *s = bs->opaque;
|
||||||
|
|
||||||
|
if (s->latency_ns) {
|
||||||
|
co_aio_sleep_ns(bdrv_get_aio_context(bs), QEMU_CLOCK_REALTIME,
|
||||||
|
s->latency_ns);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static coroutine_fn int null_co_readv(BlockDriverState *bs,
|
static coroutine_fn int null_co_readv(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors,
|
int64_t sector_num, int nb_sectors,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov)
|
||||||
{
|
{
|
||||||
return 0;
|
return null_co_common(bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int null_co_writev(BlockDriverState *bs,
|
static coroutine_fn int null_co_writev(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors,
|
int64_t sector_num, int nb_sectors,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov)
|
||||||
{
|
{
|
||||||
return 0;
|
return null_co_common(bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int null_co_flush(BlockDriverState *bs)
|
static coroutine_fn int null_co_flush(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
return 0;
|
return null_co_common(bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BlockAIOCB common;
|
BlockAIOCB common;
|
||||||
QEMUBH *bh;
|
QEMUBH *bh;
|
||||||
|
QEMUTimer timer;
|
||||||
} NullAIOCB;
|
} NullAIOCB;
|
||||||
|
|
||||||
static const AIOCBInfo null_aiocb_info = {
|
static const AIOCBInfo null_aiocb_info = {
|
||||||
|
@ -94,15 +122,33 @@ static void null_bh_cb(void *opaque)
|
||||||
qemu_aio_unref(acb);
|
qemu_aio_unref(acb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void null_timer_cb(void *opaque)
|
||||||
|
{
|
||||||
|
NullAIOCB *acb = opaque;
|
||||||
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
|
timer_deinit(&acb->timer);
|
||||||
|
qemu_aio_unref(acb);
|
||||||
|
}
|
||||||
|
|
||||||
static inline BlockAIOCB *null_aio_common(BlockDriverState *bs,
|
static inline BlockAIOCB *null_aio_common(BlockDriverState *bs,
|
||||||
BlockCompletionFunc *cb,
|
BlockCompletionFunc *cb,
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
NullAIOCB *acb;
|
NullAIOCB *acb;
|
||||||
|
BDRVNullState *s = bs->opaque;
|
||||||
|
|
||||||
acb = qemu_aio_get(&null_aiocb_info, bs, cb, opaque);
|
acb = qemu_aio_get(&null_aiocb_info, bs, cb, opaque);
|
||||||
acb->bh = aio_bh_new(bdrv_get_aio_context(bs), null_bh_cb, acb);
|
/* Only emulate latency after vcpu is running. */
|
||||||
qemu_bh_schedule(acb->bh);
|
if (s->latency_ns) {
|
||||||
|
aio_timer_init(bdrv_get_aio_context(bs), &acb->timer,
|
||||||
|
QEMU_CLOCK_REALTIME, SCALE_NS,
|
||||||
|
null_timer_cb, acb);
|
||||||
|
timer_mod_ns(&acb->timer,
|
||||||
|
qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + s->latency_ns);
|
||||||
|
} else {
|
||||||
|
acb->bh = aio_bh_new(bdrv_get_aio_context(bs), null_bh_cb, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
|
}
|
||||||
return &acb->common;
|
return &acb->common;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +177,12 @@ static BlockAIOCB *null_aio_flush(BlockDriverState *bs,
|
||||||
return null_aio_common(bs, cb, opaque);
|
return null_aio_common(bs, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int null_reopen_prepare(BDRVReopenState *reopen_state,
|
||||||
|
BlockReopenQueue *queue, Error **errp)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static BlockDriver bdrv_null_co = {
|
static BlockDriver bdrv_null_co = {
|
||||||
.format_name = "null-co",
|
.format_name = "null-co",
|
||||||
.protocol_name = "null-co",
|
.protocol_name = "null-co",
|
||||||
|
@ -143,6 +195,7 @@ static BlockDriver bdrv_null_co = {
|
||||||
.bdrv_co_readv = null_co_readv,
|
.bdrv_co_readv = null_co_readv,
|
||||||
.bdrv_co_writev = null_co_writev,
|
.bdrv_co_writev = null_co_writev,
|
||||||
.bdrv_co_flush_to_disk = null_co_flush,
|
.bdrv_co_flush_to_disk = null_co_flush,
|
||||||
|
.bdrv_reopen_prepare = null_reopen_prepare,
|
||||||
};
|
};
|
||||||
|
|
||||||
static BlockDriver bdrv_null_aio = {
|
static BlockDriver bdrv_null_aio = {
|
||||||
|
@ -157,6 +210,7 @@ static BlockDriver bdrv_null_aio = {
|
||||||
.bdrv_aio_readv = null_aio_readv,
|
.bdrv_aio_readv = null_aio_readv,
|
||||||
.bdrv_aio_writev = null_aio_writev,
|
.bdrv_aio_writev = null_aio_writev,
|
||||||
.bdrv_aio_flush = null_aio_flush,
|
.bdrv_aio_flush = null_aio_flush,
|
||||||
|
.bdrv_reopen_prepare = null_reopen_prepare,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_null_init(void)
|
static void bdrv_null_init(void)
|
||||||
|
|
46
block/qapi.c
46
block/qapi.c
|
@ -31,8 +31,10 @@
|
||||||
#include "qapi/qmp/types.h"
|
#include "qapi/qmp/types.h"
|
||||||
#include "sysemu/block-backend.h"
|
#include "sysemu/block-backend.h"
|
||||||
|
|
||||||
BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs)
|
BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs, Error **errp)
|
||||||
{
|
{
|
||||||
|
ImageInfo **p_image_info;
|
||||||
|
BlockDriverState *bs0;
|
||||||
BlockDeviceInfo *info = g_malloc0(sizeof(*info));
|
BlockDeviceInfo *info = g_malloc0(sizeof(*info));
|
||||||
|
|
||||||
info->file = g_strdup(bs->filename);
|
info->file = g_strdup(bs->filename);
|
||||||
|
@ -92,6 +94,25 @@ BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs)
|
||||||
|
|
||||||
info->write_threshold = bdrv_write_threshold_get(bs);
|
info->write_threshold = bdrv_write_threshold_get(bs);
|
||||||
|
|
||||||
|
bs0 = bs;
|
||||||
|
p_image_info = &info->image;
|
||||||
|
while (1) {
|
||||||
|
Error *local_err = NULL;
|
||||||
|
bdrv_query_image_info(bs0, p_image_info, &local_err);
|
||||||
|
if (local_err) {
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
qapi_free_BlockDeviceInfo(info);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (bs0->drv && bs0->backing_hd) {
|
||||||
|
bs0 = bs0->backing_hd;
|
||||||
|
(*p_image_info)->has_backing_image = true;
|
||||||
|
p_image_info = &((*p_image_info)->backing_image);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,9 +285,6 @@ static void bdrv_query_info(BlockBackend *blk, BlockInfo **p_info,
|
||||||
{
|
{
|
||||||
BlockInfo *info = g_malloc0(sizeof(*info));
|
BlockInfo *info = g_malloc0(sizeof(*info));
|
||||||
BlockDriverState *bs = blk_bs(blk);
|
BlockDriverState *bs = blk_bs(blk);
|
||||||
BlockDriverState *bs0;
|
|
||||||
ImageInfo **p_image_info;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
info->device = g_strdup(blk_name(blk));
|
info->device = g_strdup(blk_name(blk));
|
||||||
info->type = g_strdup("unknown");
|
info->type = g_strdup("unknown");
|
||||||
info->locked = blk_dev_is_medium_locked(blk);
|
info->locked = blk_dev_is_medium_locked(blk);
|
||||||
|
@ -289,23 +307,9 @@ static void bdrv_query_info(BlockBackend *blk, BlockInfo **p_info,
|
||||||
|
|
||||||
if (bs->drv) {
|
if (bs->drv) {
|
||||||
info->has_inserted = true;
|
info->has_inserted = true;
|
||||||
info->inserted = bdrv_block_device_info(bs);
|
info->inserted = bdrv_block_device_info(bs, errp);
|
||||||
|
if (info->inserted == NULL) {
|
||||||
bs0 = bs;
|
goto err;
|
||||||
p_image_info = &info->inserted->image;
|
|
||||||
while (1) {
|
|
||||||
bdrv_query_image_info(bs0, p_image_info, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
if (bs0->drv && bs0->backing_hd) {
|
|
||||||
bs0 = bs0->backing_hd;
|
|
||||||
(*p_image_info)->has_backing_image = true;
|
|
||||||
p_image_info = &((*p_image_info)->backing_image);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
snprintf(version, sizeof(version), "QCOW version %" PRIu32,
|
snprintf(version, sizeof(version), "QCOW version %" PRIu32,
|
||||||
header.version);
|
header.version);
|
||||||
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
||||||
bdrv_get_device_name(bs), "qcow", version);
|
bdrv_get_device_or_node_name(bs), "qcow", version);
|
||||||
ret = -ENOTSUP;
|
ret = -ENOTSUP;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -229,9 +229,9 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disable migration when qcow images are used */
|
/* Disable migration when qcow images are used */
|
||||||
error_set(&s->migration_blocker,
|
error_setg(&s->migration_blocker, "The qcow format used by node '%s' "
|
||||||
QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
"does not support live migration",
|
||||||
"qcow", bdrv_get_device_name(bs), "live migration");
|
bdrv_get_device_or_node_name(bs));
|
||||||
migrate_add_blocker(s->migration_blocker);
|
migrate_add_blocker(s->migration_blocker);
|
||||||
|
|
||||||
qemu_co_mutex_init(&s->lock);
|
qemu_co_mutex_init(&s->lock);
|
||||||
|
|
|
@ -2450,7 +2450,7 @@ int qcow2_pre_write_overlap_check(BlockDriverState *bs, int ign, int64_t offset,
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
} else if (ret > 0) {
|
} else if (ret > 0) {
|
||||||
int metadata_ol_bitnr = ffs(ret) - 1;
|
int metadata_ol_bitnr = ctz32(ret);
|
||||||
assert(metadata_ol_bitnr < QCOW2_OL_MAX_BITNR);
|
assert(metadata_ol_bitnr < QCOW2_OL_MAX_BITNR);
|
||||||
|
|
||||||
qcow2_signal_corruption(bs, true, offset, size, "Preventing invalid "
|
qcow2_signal_corruption(bs, true, offset, size, "Preventing invalid "
|
||||||
|
|
|
@ -351,10 +351,8 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
|
||||||
|
|
||||||
memset(sn, 0, sizeof(*sn));
|
memset(sn, 0, sizeof(*sn));
|
||||||
|
|
||||||
/* Generate an ID if it wasn't passed */
|
/* Generate an ID */
|
||||||
if (sn_info->id_str[0] == '\0') {
|
find_new_snapshot_id(bs, sn_info->id_str, sizeof(sn_info->id_str));
|
||||||
find_new_snapshot_id(bs, sn_info->id_str, sizeof(sn_info->id_str));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check that the ID is unique */
|
/* Check that the ID is unique */
|
||||||
if (find_snapshot_by_id_and_name(bs, sn_info->id_str, NULL) >= 0) {
|
if (find_snapshot_by_id_and_name(bs, sn_info->id_str, NULL) >= 0) {
|
||||||
|
|
|
@ -208,7 +208,7 @@ static void GCC_FMT_ATTR(3, 4) report_unsupported(BlockDriverState *bs,
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
||||||
bdrv_get_device_name(bs), "qcow2", msg);
|
bdrv_get_device_or_node_name(bs), "qcow2", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void report_unsupported_feature(BlockDriverState *bs,
|
static void report_unsupported_feature(BlockDriverState *bs,
|
||||||
|
@ -1802,7 +1802,7 @@ static int qcow2_create2(const char *filename, int64_t total_size,
|
||||||
{
|
{
|
||||||
/* Calculate cluster_bits */
|
/* Calculate cluster_bits */
|
||||||
int cluster_bits;
|
int cluster_bits;
|
||||||
cluster_bits = ffs(cluster_size) - 1;
|
cluster_bits = ctz32(cluster_size);
|
||||||
if (cluster_bits < MIN_CLUSTER_BITS || cluster_bits > MAX_CLUSTER_BITS ||
|
if (cluster_bits < MIN_CLUSTER_BITS || cluster_bits > MAX_CLUSTER_BITS ||
|
||||||
(1 << cluster_bits) != cluster_size)
|
(1 << cluster_bits) != cluster_size)
|
||||||
{
|
{
|
||||||
|
@ -2110,7 +2110,7 @@ static int qcow2_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
refcount_order = ffs(refcount_bits) - 1;
|
refcount_order = ctz32(refcount_bits);
|
||||||
|
|
||||||
ret = qcow2_create2(filename, size, backing_file, backing_fmt, flags,
|
ret = qcow2_create2(filename, size, backing_file, backing_fmt, flags,
|
||||||
cluster_size, prealloc, opts, version, refcount_order,
|
cluster_size, prealloc, opts, version, refcount_order,
|
||||||
|
@ -2824,6 +2824,7 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
|
||||||
int64_t size, const char *message_format, ...)
|
int64_t size, const char *message_format, ...)
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
|
const char *node_name;
|
||||||
char *message;
|
char *message;
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
|
@ -2847,8 +2848,11 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
|
||||||
"corruption events will be suppressed\n", message);
|
"corruption events will be suppressed\n", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
qapi_event_send_block_image_corrupted(bdrv_get_device_name(bs), message,
|
node_name = bdrv_get_node_name(bs);
|
||||||
offset >= 0, offset, size >= 0, size,
|
qapi_event_send_block_image_corrupted(bdrv_get_device_name(bs),
|
||||||
|
*node_name != '\0', node_name,
|
||||||
|
message, offset >= 0, offset,
|
||||||
|
size >= 0, size,
|
||||||
fatal, &error_abort);
|
fatal, &error_abort);
|
||||||
g_free(message);
|
g_free(message);
|
||||||
|
|
||||||
|
|
|
@ -408,7 +408,7 @@ static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
snprintf(buf, sizeof(buf), "%" PRIx64,
|
snprintf(buf, sizeof(buf), "%" PRIx64,
|
||||||
s->header.features & ~QED_FEATURE_MASK);
|
s->header.features & ~QED_FEATURE_MASK);
|
||||||
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
||||||
bdrv_get_device_name(bs), "QED", buf);
|
bdrv_get_device_or_node_name(bs), "QED", buf);
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
if (!qed_is_cluster_size_valid(s->header.cluster_size)) {
|
if (!qed_is_cluster_size_valid(s->header.cluster_size)) {
|
||||||
|
@ -436,9 +436,9 @@ static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
|
|
||||||
s->table_nelems = (s->header.cluster_size * s->header.table_size) /
|
s->table_nelems = (s->header.cluster_size * s->header.table_size) /
|
||||||
sizeof(uint64_t);
|
sizeof(uint64_t);
|
||||||
s->l2_shift = ffs(s->header.cluster_size) - 1;
|
s->l2_shift = ctz32(s->header.cluster_size);
|
||||||
s->l2_mask = s->table_nelems - 1;
|
s->l2_mask = s->table_nelems - 1;
|
||||||
s->l1_shift = s->l2_shift + ffs(s->table_nelems) - 1;
|
s->l1_shift = s->l2_shift + ctz32(s->table_nelems);
|
||||||
|
|
||||||
/* Header size calculation must not overflow uint32_t */
|
/* Header size calculation must not overflow uint32_t */
|
||||||
if (s->header.header_size > UINT32_MAX / s->header.cluster_size) {
|
if (s->header.header_size > UINT32_MAX / s->header.cluster_size) {
|
||||||
|
|
|
@ -226,10 +226,7 @@ static void quorum_report_bad(QuorumAIOCB *acb, char *node_name, int ret)
|
||||||
|
|
||||||
static void quorum_report_failure(QuorumAIOCB *acb)
|
static void quorum_report_failure(QuorumAIOCB *acb)
|
||||||
{
|
{
|
||||||
const char *reference = bdrv_get_device_name(acb->common.bs)[0] ?
|
const char *reference = bdrv_get_device_or_node_name(acb->common.bs);
|
||||||
bdrv_get_device_name(acb->common.bs) :
|
|
||||||
acb->common.bs->node_name;
|
|
||||||
|
|
||||||
qapi_event_send_quorum_failure(reference, acb->sector_num,
|
qapi_event_send_quorum_failure(reference, acb->sector_num,
|
||||||
acb->nb_sectors, &error_abort);
|
acb->nb_sectors, &error_abort);
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,7 +325,7 @@ static int qemu_rbd_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||||
error_setg(errp, "obj size too small");
|
error_setg(errp, "obj size too small");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
obj_order = ffs(objsize) - 1;
|
obj_order = ctz32(objsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientname = qemu_rbd_parse_clientname(conf, clientname_buf);
|
clientname = qemu_rbd_parse_clientname(conf, clientname_buf);
|
||||||
|
|
|
@ -1716,7 +1716,7 @@ static int parse_block_size_shift(BDRVSheepdogState *s, QemuOpts *opt)
|
||||||
if ((object_size - 1) & object_size) { /* not a power of 2? */
|
if ((object_size - 1) & object_size) { /* not a power of 2? */
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
obj_order = ffs(object_size) - 1;
|
obj_order = ctz32(object_size);
|
||||||
if (obj_order < 20 || obj_order > 31) {
|
if (obj_order < 20 || obj_order > 31) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,9 +246,9 @@ int bdrv_snapshot_delete(BlockDriverState *bs,
|
||||||
if (bs->file) {
|
if (bs->file) {
|
||||||
return bdrv_snapshot_delete(bs->file, snapshot_id, name, errp);
|
return bdrv_snapshot_delete(bs->file, snapshot_id, name, errp);
|
||||||
}
|
}
|
||||||
error_set(errp, QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
error_setg(errp, "Block format '%s' used by device '%s' "
|
||||||
drv->format_name, bdrv_get_device_name(bs),
|
"does not support internal snapshot deletion",
|
||||||
"internal snapshot deletion");
|
drv->format_name, bdrv_get_device_name(bs));
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,9 +329,9 @@ int bdrv_snapshot_load_tmp(BlockDriverState *bs,
|
||||||
if (drv->bdrv_snapshot_load_tmp) {
|
if (drv->bdrv_snapshot_load_tmp) {
|
||||||
return drv->bdrv_snapshot_load_tmp(bs, snapshot_id, name, errp);
|
return drv->bdrv_snapshot_load_tmp(bs, snapshot_id, name, errp);
|
||||||
}
|
}
|
||||||
error_set(errp, QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
error_setg(errp, "Block format '%s' used by device '%s' "
|
||||||
drv->format_name, bdrv_get_device_name(bs),
|
"does not support temporarily loading internal snapshots",
|
||||||
"temporarily load internal snapshot");
|
drv->format_name, bdrv_get_device_name(bs));
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -502,9 +502,9 @@ static int vdi_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disable migration when vdi images are used */
|
/* Disable migration when vdi images are used */
|
||||||
error_set(&s->migration_blocker,
|
error_setg(&s->migration_blocker, "The vdi format used by node '%s' "
|
||||||
QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
"does not support live migration",
|
||||||
"vdi", bdrv_get_device_name(bs), "live migration");
|
bdrv_get_device_or_node_name(bs));
|
||||||
migrate_add_blocker(s->migration_blocker);
|
migrate_add_blocker(s->migration_blocker);
|
||||||
|
|
||||||
qemu_co_mutex_init(&s->write_lock);
|
qemu_co_mutex_init(&s->write_lock);
|
||||||
|
|
10
block/vhdx.c
10
block/vhdx.c
|
@ -1002,9 +1002,9 @@ static int vhdx_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
/* TODO: differencing files */
|
/* TODO: differencing files */
|
||||||
|
|
||||||
/* Disable migration when VHDX images are used */
|
/* Disable migration when VHDX images are used */
|
||||||
error_set(&s->migration_blocker,
|
error_setg(&s->migration_blocker, "The vhdx format used by node '%s' "
|
||||||
QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
"does not support live migration",
|
||||||
"vhdx", bdrv_get_device_name(bs), "live migration");
|
bdrv_get_device_or_node_name(bs));
|
||||||
migrate_add_blocker(s->migration_blocker);
|
migrate_add_blocker(s->migration_blocker);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1269,7 +1269,7 @@ static coroutine_fn int vhdx_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
iov1.iov_base = qemu_blockalign(bs, iov1.iov_len);
|
iov1.iov_base = qemu_blockalign(bs, iov1.iov_len);
|
||||||
memset(iov1.iov_base, 0, iov1.iov_len);
|
memset(iov1.iov_base, 0, iov1.iov_len);
|
||||||
qemu_iovec_concat_iov(&hd_qiov, &iov1, 1, 0,
|
qemu_iovec_concat_iov(&hd_qiov, &iov1, 1, 0,
|
||||||
sinfo.block_offset);
|
iov1.iov_len);
|
||||||
sectors_to_write += iov1.iov_len >> BDRV_SECTOR_BITS;
|
sectors_to_write += iov1.iov_len >> BDRV_SECTOR_BITS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1285,7 +1285,7 @@ static coroutine_fn int vhdx_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
iov2.iov_base = qemu_blockalign(bs, iov2.iov_len);
|
iov2.iov_base = qemu_blockalign(bs, iov2.iov_len);
|
||||||
memset(iov2.iov_base, 0, iov2.iov_len);
|
memset(iov2.iov_base, 0, iov2.iov_len);
|
||||||
qemu_iovec_concat_iov(&hd_qiov, &iov2, 1, 0,
|
qemu_iovec_concat_iov(&hd_qiov, &iov2, 1, 0,
|
||||||
sinfo.block_offset);
|
iov2.iov_len);
|
||||||
sectors_to_write += iov2.iov_len >> BDRV_SECTOR_BITS;
|
sectors_to_write += iov2.iov_len >> BDRV_SECTOR_BITS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
block/vmdk.c
10
block/vmdk.c
|
@ -523,7 +523,7 @@ static int vmdk_open_vmfs_sparse(BlockDriverState *bs,
|
||||||
}
|
}
|
||||||
ret = vmdk_add_extent(bs, file, false,
|
ret = vmdk_add_extent(bs, file, false,
|
||||||
le32_to_cpu(header.disk_sectors),
|
le32_to_cpu(header.disk_sectors),
|
||||||
le32_to_cpu(header.l1dir_offset) << 9,
|
(int64_t)le32_to_cpu(header.l1dir_offset) << 9,
|
||||||
0,
|
0,
|
||||||
le32_to_cpu(header.l1dir_size),
|
le32_to_cpu(header.l1dir_size),
|
||||||
4096,
|
4096,
|
||||||
|
@ -669,7 +669,7 @@ static int vmdk_open_vmdk4(BlockDriverState *bs,
|
||||||
snprintf(buf, sizeof(buf), "VMDK version %" PRId32,
|
snprintf(buf, sizeof(buf), "VMDK version %" PRId32,
|
||||||
le32_to_cpu(header.version));
|
le32_to_cpu(header.version));
|
||||||
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
||||||
bdrv_get_device_name(bs), "vmdk", buf);
|
bdrv_get_device_or_node_name(bs), "vmdk", buf);
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
} else if (le32_to_cpu(header.version) == 3 && (flags & BDRV_O_RDWR)) {
|
} else if (le32_to_cpu(header.version) == 3 && (flags & BDRV_O_RDWR)) {
|
||||||
/* VMware KB 2064959 explains that version 3 added support for
|
/* VMware KB 2064959 explains that version 3 added support for
|
||||||
|
@ -962,9 +962,9 @@ static int vmdk_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
qemu_co_mutex_init(&s->lock);
|
qemu_co_mutex_init(&s->lock);
|
||||||
|
|
||||||
/* Disable migration when VMDK images are used */
|
/* Disable migration when VMDK images are used */
|
||||||
error_set(&s->migration_blocker,
|
error_setg(&s->migration_blocker, "The vmdk format used by node '%s' "
|
||||||
QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
"does not support live migration",
|
||||||
"vmdk", bdrv_get_device_name(bs), "live migration");
|
bdrv_get_device_or_node_name(bs));
|
||||||
migrate_add_blocker(s->migration_blocker);
|
migrate_add_blocker(s->migration_blocker);
|
||||||
g_free(buf);
|
g_free(buf);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -318,9 +318,9 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
qemu_co_mutex_init(&s->lock);
|
qemu_co_mutex_init(&s->lock);
|
||||||
|
|
||||||
/* Disable migration when VHD images are used */
|
/* Disable migration when VHD images are used */
|
||||||
error_set(&s->migration_blocker,
|
error_setg(&s->migration_blocker, "The vpc format used by node '%s' "
|
||||||
QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
"does not support live migration",
|
||||||
"vpc", bdrv_get_device_name(bs), "live migration");
|
bdrv_get_device_or_node_name(bs));
|
||||||
migrate_add_blocker(s->migration_blocker);
|
migrate_add_blocker(s->migration_blocker);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -1180,9 +1180,10 @@ static int vvfat_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
|
|
||||||
/* Disable migration when vvfat is used rw */
|
/* Disable migration when vvfat is used rw */
|
||||||
if (s->qcow) {
|
if (s->qcow) {
|
||||||
error_set(&s->migration_blocker,
|
error_setg(&s->migration_blocker,
|
||||||
QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
"The vvfat (rw) format used by node '%s' "
|
||||||
"vvfat (rw)", bdrv_get_device_name(bs), "live migration");
|
"does not support live migration",
|
||||||
|
bdrv_get_device_or_node_name(bs));
|
||||||
migrate_add_blocker(s->migration_blocker);
|
migrate_add_blocker(s->migration_blocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
195
blockdev.c
195
blockdev.c
|
@ -1164,6 +1164,68 @@ out_aio_context:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* block_dirty_bitmap_lookup:
|
||||||
|
* Return a dirty bitmap (if present), after validating
|
||||||
|
* the node reference and bitmap names.
|
||||||
|
*
|
||||||
|
* @node: The name of the BDS node to search for bitmaps
|
||||||
|
* @name: The name of the bitmap to search for
|
||||||
|
* @pbs: Output pointer for BDS lookup, if desired. Can be NULL.
|
||||||
|
* @paio: Output pointer for aio_context acquisition, if desired. Can be NULL.
|
||||||
|
* @errp: Output pointer for error information. Can be NULL.
|
||||||
|
*
|
||||||
|
* @return: A bitmap object on success, or NULL on failure.
|
||||||
|
*/
|
||||||
|
static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,
|
||||||
|
const char *name,
|
||||||
|
BlockDriverState **pbs,
|
||||||
|
AioContext **paio,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
BlockDriverState *bs;
|
||||||
|
BdrvDirtyBitmap *bitmap;
|
||||||
|
AioContext *aio_context;
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
error_setg(errp, "Node cannot be NULL");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!name) {
|
||||||
|
error_setg(errp, "Bitmap name cannot be NULL");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
bs = bdrv_lookup_bs(node, node, NULL);
|
||||||
|
if (!bs) {
|
||||||
|
error_setg(errp, "Node '%s' not found", node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
aio_context = bdrv_get_aio_context(bs);
|
||||||
|
aio_context_acquire(aio_context);
|
||||||
|
|
||||||
|
bitmap = bdrv_find_dirty_bitmap(bs, name);
|
||||||
|
if (!bitmap) {
|
||||||
|
error_setg(errp, "Dirty bitmap '%s' not found", name);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pbs) {
|
||||||
|
*pbs = bs;
|
||||||
|
}
|
||||||
|
if (paio) {
|
||||||
|
*paio = aio_context;
|
||||||
|
} else {
|
||||||
|
aio_context_release(aio_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
aio_context_release(aio_context);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* New and old BlockDriverState structs for atomic group operations */
|
/* New and old BlockDriverState structs for atomic group operations */
|
||||||
|
|
||||||
typedef struct BlkTransactionState BlkTransactionState;
|
typedef struct BlkTransactionState BlkTransactionState;
|
||||||
|
@ -1248,13 +1310,14 @@ static void internal_snapshot_prepare(BlkTransactionState *common,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bdrv_is_read_only(bs)) {
|
if (bdrv_is_read_only(bs)) {
|
||||||
error_set(errp, QERR_DEVICE_IS_READ_ONLY, device);
|
error_setg(errp, "Device '%s' is read only", device);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bdrv_can_snapshot(bs)) {
|
if (!bdrv_can_snapshot(bs)) {
|
||||||
error_set(errp, QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
|
error_setg(errp, "Block format '%s' used by device '%s' "
|
||||||
bs->drv->format_name, device, "internal snapshot");
|
"does not support internal snapshots",
|
||||||
|
bs->drv->format_name, device);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1522,6 +1585,7 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
|
||||||
backup->sync,
|
backup->sync,
|
||||||
backup->has_mode, backup->mode,
|
backup->has_mode, backup->mode,
|
||||||
backup->has_speed, backup->speed,
|
backup->has_speed, backup->speed,
|
||||||
|
backup->has_bitmap, backup->bitmap,
|
||||||
backup->has_on_source_error, backup->on_source_error,
|
backup->has_on_source_error, backup->on_source_error,
|
||||||
backup->has_on_target_error, backup->on_target_error,
|
backup->has_on_target_error, backup->on_target_error,
|
||||||
&local_err);
|
&local_err);
|
||||||
|
@ -1953,6 +2017,102 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd,
|
||||||
aio_context_release(aio_context);
|
aio_context_release(aio_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void qmp_block_dirty_bitmap_add(const char *node, const char *name,
|
||||||
|
bool has_granularity, uint32_t granularity,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
AioContext *aio_context;
|
||||||
|
BlockDriverState *bs;
|
||||||
|
|
||||||
|
if (!name || name[0] == '\0') {
|
||||||
|
error_setg(errp, "Bitmap name cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bs = bdrv_lookup_bs(node, node, errp);
|
||||||
|
if (!bs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
aio_context = bdrv_get_aio_context(bs);
|
||||||
|
aio_context_acquire(aio_context);
|
||||||
|
|
||||||
|
if (has_granularity) {
|
||||||
|
if (granularity < 512 || !is_power_of_2(granularity)) {
|
||||||
|
error_setg(errp, "Granularity must be power of 2 "
|
||||||
|
"and at least 512");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Default to cluster size, if available: */
|
||||||
|
granularity = bdrv_get_default_bitmap_granularity(bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bdrv_create_dirty_bitmap(bs, granularity, name, errp);
|
||||||
|
|
||||||
|
out:
|
||||||
|
aio_context_release(aio_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qmp_block_dirty_bitmap_remove(const char *node, const char *name,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
AioContext *aio_context;
|
||||||
|
BlockDriverState *bs;
|
||||||
|
BdrvDirtyBitmap *bitmap;
|
||||||
|
|
||||||
|
bitmap = block_dirty_bitmap_lookup(node, name, &bs, &aio_context, errp);
|
||||||
|
if (!bitmap || !bs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bdrv_dirty_bitmap_frozen(bitmap)) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Bitmap '%s' is currently frozen and cannot be removed",
|
||||||
|
name);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
bdrv_dirty_bitmap_make_anon(bitmap);
|
||||||
|
bdrv_release_dirty_bitmap(bs, bitmap);
|
||||||
|
|
||||||
|
out:
|
||||||
|
aio_context_release(aio_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely clear a bitmap, for the purposes of synchronizing a bitmap
|
||||||
|
* immediately after a full backup operation.
|
||||||
|
*/
|
||||||
|
void qmp_block_dirty_bitmap_clear(const char *node, const char *name,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
AioContext *aio_context;
|
||||||
|
BdrvDirtyBitmap *bitmap;
|
||||||
|
BlockDriverState *bs;
|
||||||
|
|
||||||
|
bitmap = block_dirty_bitmap_lookup(node, name, &bs, &aio_context, errp);
|
||||||
|
if (!bitmap || !bs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bdrv_dirty_bitmap_frozen(bitmap)) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Bitmap '%s' is currently frozen and cannot be modified",
|
||||||
|
name);
|
||||||
|
goto out;
|
||||||
|
} else if (!bdrv_dirty_bitmap_enabled(bitmap)) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Bitmap '%s' is currently disabled and cannot be cleared",
|
||||||
|
name);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bdrv_clear_dirty_bitmap(bitmap);
|
||||||
|
|
||||||
|
out:
|
||||||
|
aio_context_release(aio_context);
|
||||||
|
}
|
||||||
|
|
||||||
int hmp_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
int hmp_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||||
{
|
{
|
||||||
const char *id = qdict_get_str(qdict, "id");
|
const char *id = qdict_get_str(qdict, "id");
|
||||||
|
@ -2055,7 +2215,7 @@ void qmp_block_resize(bool has_device, const char *device,
|
||||||
error_set(errp, QERR_UNSUPPORTED);
|
error_set(errp, QERR_UNSUPPORTED);
|
||||||
break;
|
break;
|
||||||
case -EACCES:
|
case -EACCES:
|
||||||
error_set(errp, QERR_DEVICE_IS_READ_ONLY, device);
|
error_setg(errp, "Device '%s' is read only", device);
|
||||||
break;
|
break;
|
||||||
case -EBUSY:
|
case -EBUSY:
|
||||||
error_set(errp, QERR_DEVICE_IN_USE, device);
|
error_set(errp, QERR_DEVICE_IN_USE, device);
|
||||||
|
@ -2270,6 +2430,7 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||||
enum MirrorSyncMode sync,
|
enum MirrorSyncMode sync,
|
||||||
bool has_mode, enum NewImageMode mode,
|
bool has_mode, enum NewImageMode mode,
|
||||||
bool has_speed, int64_t speed,
|
bool has_speed, int64_t speed,
|
||||||
|
bool has_bitmap, const char *bitmap,
|
||||||
bool has_on_source_error, BlockdevOnError on_source_error,
|
bool has_on_source_error, BlockdevOnError on_source_error,
|
||||||
bool has_on_target_error, BlockdevOnError on_target_error,
|
bool has_on_target_error, BlockdevOnError on_target_error,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
|
@ -2278,6 +2439,7 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
BlockDriverState *target_bs;
|
BlockDriverState *target_bs;
|
||||||
BlockDriverState *source = NULL;
|
BlockDriverState *source = NULL;
|
||||||
|
BdrvDirtyBitmap *bmap = NULL;
|
||||||
AioContext *aio_context;
|
AioContext *aio_context;
|
||||||
BlockDriver *drv = NULL;
|
BlockDriver *drv = NULL;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
@ -2377,7 +2539,16 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||||
|
|
||||||
bdrv_set_aio_context(target_bs, aio_context);
|
bdrv_set_aio_context(target_bs, aio_context);
|
||||||
|
|
||||||
backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
|
if (has_bitmap) {
|
||||||
|
bmap = bdrv_find_dirty_bitmap(bs, bitmap);
|
||||||
|
if (!bmap) {
|
||||||
|
error_setg(errp, "Bitmap '%s' could not be found", bitmap);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_start(bs, target_bs, speed, sync, bmap,
|
||||||
|
on_source_error, on_target_error,
|
||||||
block_job_cb, bs, &local_err);
|
block_job_cb, bs, &local_err);
|
||||||
if (local_err != NULL) {
|
if (local_err != NULL) {
|
||||||
bdrv_unref(target_bs);
|
bdrv_unref(target_bs);
|
||||||
|
@ -2391,7 +2562,7 @@ out:
|
||||||
|
|
||||||
BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
|
BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
|
||||||
{
|
{
|
||||||
return bdrv_named_nodes_list();
|
return bdrv_named_nodes_list(errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qmp_blockdev_backup(const char *device, const char *target,
|
void qmp_blockdev_backup(const char *device, const char *target,
|
||||||
|
@ -2438,8 +2609,8 @@ void qmp_blockdev_backup(const char *device, const char *target,
|
||||||
|
|
||||||
bdrv_ref(target_bs);
|
bdrv_ref(target_bs);
|
||||||
bdrv_set_aio_context(target_bs, aio_context);
|
bdrv_set_aio_context(target_bs, aio_context);
|
||||||
backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
|
backup_start(bs, target_bs, speed, sync, NULL, on_source_error,
|
||||||
block_job_cb, bs, &local_err);
|
on_target_error, block_job_cb, bs, &local_err);
|
||||||
if (local_err != NULL) {
|
if (local_err != NULL) {
|
||||||
bdrv_unref(target_bs);
|
bdrv_unref(target_bs);
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
|
@ -2699,7 +2870,7 @@ void qmp_block_job_cancel(const char *device,
|
||||||
force = false;
|
force = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (job->paused && !force) {
|
if (job->user_paused && !force) {
|
||||||
error_setg(errp, "The block job for device '%s' is currently paused",
|
error_setg(errp, "The block job for device '%s' is currently paused",
|
||||||
device);
|
device);
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -2716,10 +2887,11 @@ void qmp_block_job_pause(const char *device, Error **errp)
|
||||||
AioContext *aio_context;
|
AioContext *aio_context;
|
||||||
BlockJob *job = find_block_job(device, &aio_context, errp);
|
BlockJob *job = find_block_job(device, &aio_context, errp);
|
||||||
|
|
||||||
if (!job) {
|
if (!job || job->user_paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
job->user_paused = true;
|
||||||
trace_qmp_block_job_pause(job);
|
trace_qmp_block_job_pause(job);
|
||||||
block_job_pause(job);
|
block_job_pause(job);
|
||||||
aio_context_release(aio_context);
|
aio_context_release(aio_context);
|
||||||
|
@ -2730,10 +2902,11 @@ void qmp_block_job_resume(const char *device, Error **errp)
|
||||||
AioContext *aio_context;
|
AioContext *aio_context;
|
||||||
BlockJob *job = find_block_job(device, &aio_context, errp);
|
BlockJob *job = find_block_job(device, &aio_context, errp);
|
||||||
|
|
||||||
if (!job) {
|
if (!job || !job->user_paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
job->user_paused = false;
|
||||||
trace_qmp_block_job_resume(job);
|
trace_qmp_block_job_resume(job);
|
||||||
block_job_resume(job);
|
block_job_resume(job);
|
||||||
aio_context_release(aio_context);
|
aio_context_release(aio_context);
|
||||||
|
|
23
blockjob.c
23
blockjob.c
|
@ -107,7 +107,7 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||||
|
|
||||||
void block_job_complete(BlockJob *job, Error **errp)
|
void block_job_complete(BlockJob *job, Error **errp)
|
||||||
{
|
{
|
||||||
if (job->paused || job->cancelled || !job->driver->complete) {
|
if (job->pause_count || job->cancelled || !job->driver->complete) {
|
||||||
error_set(errp, QERR_BLOCK_JOB_NOT_READY,
|
error_set(errp, QERR_BLOCK_JOB_NOT_READY,
|
||||||
bdrv_get_device_name(job->bs));
|
bdrv_get_device_name(job->bs));
|
||||||
return;
|
return;
|
||||||
|
@ -118,17 +118,26 @@ void block_job_complete(BlockJob *job, Error **errp)
|
||||||
|
|
||||||
void block_job_pause(BlockJob *job)
|
void block_job_pause(BlockJob *job)
|
||||||
{
|
{
|
||||||
job->paused = true;
|
job->pause_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool block_job_is_paused(BlockJob *job)
|
bool block_job_is_paused(BlockJob *job)
|
||||||
{
|
{
|
||||||
return job->paused;
|
return job->pause_count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void block_job_resume(BlockJob *job)
|
void block_job_resume(BlockJob *job)
|
||||||
{
|
{
|
||||||
job->paused = false;
|
assert(job->pause_count > 0);
|
||||||
|
job->pause_count--;
|
||||||
|
if (job->pause_count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
block_job_enter(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
void block_job_enter(BlockJob *job)
|
||||||
|
{
|
||||||
block_job_iostatus_reset(job);
|
block_job_iostatus_reset(job);
|
||||||
if (job->co && !job->busy) {
|
if (job->co && !job->busy) {
|
||||||
qemu_coroutine_enter(job->co, NULL);
|
qemu_coroutine_enter(job->co, NULL);
|
||||||
|
@ -138,7 +147,7 @@ void block_job_resume(BlockJob *job)
|
||||||
void block_job_cancel(BlockJob *job)
|
void block_job_cancel(BlockJob *job)
|
||||||
{
|
{
|
||||||
job->cancelled = true;
|
job->cancelled = true;
|
||||||
block_job_resume(job);
|
block_job_enter(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool block_job_is_cancelled(BlockJob *job)
|
bool block_job_is_cancelled(BlockJob *job)
|
||||||
|
@ -258,7 +267,7 @@ BlockJobInfo *block_job_query(BlockJob *job)
|
||||||
info->device = g_strdup(bdrv_get_device_name(job->bs));
|
info->device = g_strdup(bdrv_get_device_name(job->bs));
|
||||||
info->len = job->len;
|
info->len = job->len;
|
||||||
info->busy = job->busy;
|
info->busy = job->busy;
|
||||||
info->paused = job->paused;
|
info->paused = job->pause_count > 0;
|
||||||
info->offset = job->offset;
|
info->offset = job->offset;
|
||||||
info->speed = job->speed;
|
info->speed = job->speed;
|
||||||
info->io_status = job->iostatus;
|
info->io_status = job->iostatus;
|
||||||
|
@ -335,6 +344,8 @@ BlockErrorAction block_job_error_action(BlockJob *job, BlockDriverState *bs,
|
||||||
IO_OPERATION_TYPE_WRITE,
|
IO_OPERATION_TYPE_WRITE,
|
||||||
action, &error_abort);
|
action, &error_abort);
|
||||||
if (action == BLOCK_ERROR_ACTION_STOP) {
|
if (action == BLOCK_ERROR_ACTION_STOP) {
|
||||||
|
/* make the pause user visible, which will be resumed from QMP. */
|
||||||
|
job->user_paused = true;
|
||||||
block_job_pause(job);
|
block_job_pause(job);
|
||||||
block_job_iostatus_set_err(job, error);
|
block_job_iostatus_set_err(job, error);
|
||||||
if (bs != job->bs) {
|
if (bs != job->bs) {
|
||||||
|
|
|
@ -0,0 +1,352 @@
|
||||||
|
<!--
|
||||||
|
Copyright 2015 John Snow <jsnow@redhat.com> and Red Hat, Inc.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
This file is licensed via The FreeBSD Documentation License, the full text of
|
||||||
|
which is included at the end of this document.
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Dirty Bitmaps and Incremental Backup
|
||||||
|
|
||||||
|
* Dirty Bitmaps are objects that track which data needs to be backed up for the
|
||||||
|
next incremental backup.
|
||||||
|
|
||||||
|
* Dirty bitmaps can be created at any time and attached to any node
|
||||||
|
(not just complete drives.)
|
||||||
|
|
||||||
|
## Dirty Bitmap Names
|
||||||
|
|
||||||
|
* A dirty bitmap's name is unique to the node, but bitmaps attached to different
|
||||||
|
nodes can share the same name.
|
||||||
|
|
||||||
|
## Bitmap Modes
|
||||||
|
|
||||||
|
* A Bitmap can be "frozen," which means that it is currently in-use by a backup
|
||||||
|
operation and cannot be deleted, renamed, written to, reset,
|
||||||
|
etc.
|
||||||
|
|
||||||
|
## Basic QMP Usage
|
||||||
|
|
||||||
|
### Supported Commands ###
|
||||||
|
|
||||||
|
* block-dirty-bitmap-add
|
||||||
|
* block-dirty-bitmap-remove
|
||||||
|
* block-dirty-bitmap-clear
|
||||||
|
|
||||||
|
### Creation
|
||||||
|
|
||||||
|
* To create a new bitmap, enabled, on the drive with id=drive0:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "block-dirty-bitmap-add",
|
||||||
|
"arguments": {
|
||||||
|
"node": "drive0",
|
||||||
|
"name": "bitmap0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* This bitmap will have a default granularity that matches the cluster size of
|
||||||
|
its associated drive, if available, clamped to between [4KiB, 64KiB].
|
||||||
|
The current default for qcow2 is 64KiB.
|
||||||
|
|
||||||
|
* To create a new bitmap that tracks changes in 32KiB segments:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "block-dirty-bitmap-add",
|
||||||
|
"arguments": {
|
||||||
|
"node": "drive0",
|
||||||
|
"name": "bitmap0",
|
||||||
|
"granularity": 32768
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deletion
|
||||||
|
|
||||||
|
* Bitmaps that are frozen cannot be deleted.
|
||||||
|
|
||||||
|
* Deleting the bitmap does not impact any other bitmaps attached to the same
|
||||||
|
node, nor does it affect any backups already created from this node.
|
||||||
|
|
||||||
|
* Because bitmaps are only unique to the node to which they are attached,
|
||||||
|
you must specify the node/drive name here, too.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "block-dirty-bitmap-remove",
|
||||||
|
"arguments": {
|
||||||
|
"node": "drive0",
|
||||||
|
"name": "bitmap0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resetting
|
||||||
|
|
||||||
|
* Resetting a bitmap will clear all information it holds.
|
||||||
|
|
||||||
|
* An incremental backup created from an empty bitmap will copy no data,
|
||||||
|
as if nothing has changed.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "block-dirty-bitmap-clear",
|
||||||
|
"arguments": {
|
||||||
|
"node": "drive0",
|
||||||
|
"name": "bitmap0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transactions (Not yet implemented)
|
||||||
|
|
||||||
|
* Transactional commands are forthcoming in a future version,
|
||||||
|
and are not yet available for use. This section serves as
|
||||||
|
documentation of intent for their design and usage.
|
||||||
|
|
||||||
|
### Justification
|
||||||
|
|
||||||
|
Bitmaps can be safely modified when the VM is paused or halted by using
|
||||||
|
the basic QMP commands. For instance, you might perform the following actions:
|
||||||
|
|
||||||
|
1. Boot the VM in a paused state.
|
||||||
|
2. Create a full drive backup of drive0.
|
||||||
|
3. Create a new bitmap attached to drive0.
|
||||||
|
4. Resume execution of the VM.
|
||||||
|
5. Incremental backups are ready to be created.
|
||||||
|
|
||||||
|
At this point, the bitmap and drive backup would be correctly in sync,
|
||||||
|
and incremental backups made from this point forward would be correctly aligned
|
||||||
|
to the full drive backup.
|
||||||
|
|
||||||
|
This is not particularly useful if we decide we want to start incremental
|
||||||
|
backups after the VM has been running for a while, for which we will need to
|
||||||
|
perform actions such as the following:
|
||||||
|
|
||||||
|
1. Boot the VM and begin execution.
|
||||||
|
2. Using a single transaction, perform the following operations:
|
||||||
|
* Create bitmap0.
|
||||||
|
* Create a full drive backup of drive0.
|
||||||
|
3. Incremental backups are now ready to be created.
|
||||||
|
|
||||||
|
### Supported Bitmap Transactions
|
||||||
|
|
||||||
|
* block-dirty-bitmap-add
|
||||||
|
* block-dirty-bitmap-clear
|
||||||
|
|
||||||
|
The usages are identical to their respective QMP commands, but see below
|
||||||
|
for examples.
|
||||||
|
|
||||||
|
### Example: New Incremental Backup
|
||||||
|
|
||||||
|
As outlined in the justification, perhaps we want to create a new incremental
|
||||||
|
backup chain attached to a drive.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "transaction",
|
||||||
|
"arguments": {
|
||||||
|
"actions": [
|
||||||
|
{"type": "block-dirty-bitmap-add",
|
||||||
|
"data": {"node": "drive0", "name": "bitmap0"} },
|
||||||
|
{"type": "drive-backup",
|
||||||
|
"data": {"device": "drive0", "target": "/path/to/full_backup.img",
|
||||||
|
"sync": "full", "format": "qcow2"} }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: New Incremental Backup Anchor Point
|
||||||
|
|
||||||
|
Maybe we just want to create a new full backup with an existing bitmap and
|
||||||
|
want to reset the bitmap to track the new chain.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "transaction",
|
||||||
|
"arguments": {
|
||||||
|
"actions": [
|
||||||
|
{"type": "block-dirty-bitmap-clear",
|
||||||
|
"data": {"node": "drive0", "name": "bitmap0"} },
|
||||||
|
{"type": "drive-backup",
|
||||||
|
"data": {"device": "drive0", "target": "/path/to/new_full_backup.img",
|
||||||
|
"sync": "full", "format": "qcow2"} }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Incremental Backups
|
||||||
|
|
||||||
|
The star of the show.
|
||||||
|
|
||||||
|
**Nota Bene!** Only incremental backups of entire drives are supported for now.
|
||||||
|
So despite the fact that you can attach a bitmap to any arbitrary node, they are
|
||||||
|
only currently useful when attached to the root node. This is because
|
||||||
|
drive-backup only supports drives/devices instead of arbitrary nodes.
|
||||||
|
|
||||||
|
### Example: First Incremental Backup
|
||||||
|
|
||||||
|
1. Create a full backup and sync it to the dirty bitmap, as in the transactional
|
||||||
|
examples above; or with the VM offline, manually create a full copy and then
|
||||||
|
create a new bitmap before the VM begins execution.
|
||||||
|
|
||||||
|
* Let's assume the full backup is named 'full_backup.img'.
|
||||||
|
* Let's assume the bitmap you created is 'bitmap0' attached to 'drive0'.
|
||||||
|
|
||||||
|
2. Create a destination image for the incremental backup that utilizes the
|
||||||
|
full backup as a backing image.
|
||||||
|
|
||||||
|
* Let's assume it is named 'incremental.0.img'.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Issue the incremental backup command:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "drive-backup",
|
||||||
|
"arguments": {
|
||||||
|
"device": "drive0",
|
||||||
|
"bitmap": "bitmap0",
|
||||||
|
"target": "incremental.0.img",
|
||||||
|
"format": "qcow2",
|
||||||
|
"sync": "dirty-bitmap",
|
||||||
|
"mode": "existing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Second Incremental Backup
|
||||||
|
|
||||||
|
1. Create a new destination image for the incremental backup that points to the
|
||||||
|
previous one, e.g.: 'incremental.1.img'
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# qemu-img create -f qcow2 incremental.1.img -b incremental.0.img -F qcow2
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Issue a new incremental backup command. The only difference here is that we
|
||||||
|
have changed the target image below.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "drive-backup",
|
||||||
|
"arguments": {
|
||||||
|
"device": "drive0",
|
||||||
|
"bitmap": "bitmap0",
|
||||||
|
"target": "incremental.1.img",
|
||||||
|
"format": "qcow2",
|
||||||
|
"sync": "dirty-bitmap",
|
||||||
|
"mode": "existing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
* In the event of an error that occurs after a backup job is successfully
|
||||||
|
launched, either by a direct QMP command or a QMP transaction, the user
|
||||||
|
will receive a BLOCK_JOB_COMPLETE event with a failure message, accompanied
|
||||||
|
by a BLOCK_JOB_ERROR event.
|
||||||
|
|
||||||
|
* In the case of an event being cancelled, the user will receive a
|
||||||
|
BLOCK_JOB_CANCELLED event instead of a pair of COMPLETE and ERROR events.
|
||||||
|
|
||||||
|
* In either case, the incremental backup data contained within the bitmap is
|
||||||
|
safely rolled back, and the data within the bitmap is not lost. The image
|
||||||
|
file created for the failed attempt can be safely deleted.
|
||||||
|
|
||||||
|
* Once the underlying problem is fixed (e.g. more storage space is freed up),
|
||||||
|
you can simply retry the incremental backup command with the same bitmap.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
1. Create a target image:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Attempt to create an incremental backup via QMP:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "drive-backup",
|
||||||
|
"arguments": {
|
||||||
|
"device": "drive0",
|
||||||
|
"bitmap": "bitmap0",
|
||||||
|
"target": "incremental.0.img",
|
||||||
|
"format": "qcow2",
|
||||||
|
"sync": "dirty-bitmap",
|
||||||
|
"mode": "existing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Receive an event notifying us of failure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "timestamp": { "seconds": 1424709442, "microseconds": 844524 },
|
||||||
|
"data": { "speed": 0, "offset": 0, "len": 67108864,
|
||||||
|
"error": "No space left on device",
|
||||||
|
"device": "drive1", "type": "backup" },
|
||||||
|
"event": "BLOCK_JOB_COMPLETED" }
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Delete the failed incremental, and re-create the image.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# rm incremental.0.img
|
||||||
|
# qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Retry the command after fixing the underlying problem,
|
||||||
|
such as freeing up space on the backup volume:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "execute": "drive-backup",
|
||||||
|
"arguments": {
|
||||||
|
"device": "drive0",
|
||||||
|
"bitmap": "bitmap0",
|
||||||
|
"target": "incremental.0.img",
|
||||||
|
"format": "qcow2",
|
||||||
|
"sync": "dirty-bitmap",
|
||||||
|
"mode": "existing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Receive confirmation that the job completed successfully:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "timestamp": { "seconds": 1424709668, "microseconds": 526525 },
|
||||||
|
"data": { "device": "drive1", "type": "backup",
|
||||||
|
"speed": 0, "len": 67108864, "offset": 67108864},
|
||||||
|
"event": "BLOCK_JOB_COMPLETED" }
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The FreeBSD Documentation License
|
||||||
|
|
||||||
|
Redistribution and use in source (Markdown) and 'compiled' forms (SGML, HTML,
|
||||||
|
PDF, PostScript, RTF and so forth) with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code (Markdown) must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer of this file
|
||||||
|
unmodified.
|
||||||
|
|
||||||
|
Redistributions in compiled form (transformed to other DTDs, converted to PDF,
|
||||||
|
PostScript, RTF and other formats) 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.
|
||||||
|
|
||||||
|
THIS DOCUMENTATION 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 THE COPYRIGHT HOLDER 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 DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
-->
|
|
@ -31,21 +31,26 @@ Example:
|
||||||
BLOCK_IMAGE_CORRUPTED
|
BLOCK_IMAGE_CORRUPTED
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Emitted when a disk image is being marked corrupt.
|
Emitted when a disk image is being marked corrupt. The image can be
|
||||||
|
identified by its device or node name. The 'device' field is always
|
||||||
|
present for compatibility reasons, but it can be empty ("") if the
|
||||||
|
image does not have a device name associated.
|
||||||
|
|
||||||
Data:
|
Data:
|
||||||
|
|
||||||
- "device": Device name (json-string)
|
- "device": Device name (json-string)
|
||||||
- "msg": Informative message (e.g., reason for the corruption) (json-string)
|
- "node-name": Node name (json-string, optional)
|
||||||
- "offset": If the corruption resulted from an image access, this is the access
|
- "msg": Informative message (e.g., reason for the corruption)
|
||||||
offset into the image (json-int)
|
(json-string)
|
||||||
- "size": If the corruption resulted from an image access, this is the access
|
- "offset": If the corruption resulted from an image access, this
|
||||||
size (json-int)
|
is the access offset into the image (json-int)
|
||||||
|
- "size": If the corruption resulted from an image access, this
|
||||||
|
is the access size (json-int)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
{ "event": "BLOCK_IMAGE_CORRUPTED",
|
{ "event": "BLOCK_IMAGE_CORRUPTED",
|
||||||
"data": { "device": "ide0-hd0",
|
"data": { "device": "ide0-hd0", "node-name": "node0",
|
||||||
"msg": "Prevented active L1 table overwrite", "offset": 196608,
|
"msg": "Prevented active L1 table overwrite", "offset": 196608,
|
||||||
"size": 65536 },
|
"size": 65536 },
|
||||||
"timestamp": { "seconds": 1378126126, "microseconds": 966463 } }
|
"timestamp": { "seconds": 1378126126, "microseconds": 966463 } }
|
||||||
|
|
6
hmp.c
6
hmp.c
|
@ -391,8 +391,7 @@ static void print_block_info(Monitor *mon, BlockInfo *info,
|
||||||
inserted->iops_size);
|
inserted->iops_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: inserted->image should never be null */
|
if (verbose) {
|
||||||
if (verbose && inserted->image) {
|
|
||||||
monitor_printf(mon, "\nImages:\n");
|
monitor_printf(mon, "\nImages:\n");
|
||||||
image_info = inserted->image;
|
image_info = inserted->image;
|
||||||
while (1) {
|
while (1) {
|
||||||
|
@ -1062,7 +1061,8 @@ void hmp_drive_backup(Monitor *mon, const QDict *qdict)
|
||||||
|
|
||||||
qmp_drive_backup(device, filename, !!format, format,
|
qmp_drive_backup(device, filename, !!format, format,
|
||||||
full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
|
full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
|
||||||
true, mode, false, 0, false, 0, false, 0, &err);
|
true, mode, false, 0, false, NULL,
|
||||||
|
false, 0, false, 0, &err);
|
||||||
hmp_handle_error(mon, &err);
|
hmp_handle_error(mon, &err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ static bool acpi_pcihp_pc_no_hotplug(AcpiPciHpState *s, PCIDevice *dev)
|
||||||
static void acpi_pcihp_eject_slot(AcpiPciHpState *s, unsigned bsel, unsigned slots)
|
static void acpi_pcihp_eject_slot(AcpiPciHpState *s, unsigned bsel, unsigned slots)
|
||||||
{
|
{
|
||||||
BusChild *kid, *next;
|
BusChild *kid, *next;
|
||||||
int slot = ffs(slots) - 1;
|
int slot = ctz32(slots);
|
||||||
PCIBus *bus = acpi_pcihp_find_hotplug_bus(s, bsel);
|
PCIBus *bus = acpi_pcihp_find_hotplug_bus(s, bsel);
|
||||||
|
|
||||||
if (!bus) {
|
if (!bus) {
|
||||||
|
|
|
@ -579,7 +579,10 @@ static uint32_t mipid_txrx(void *opaque, uint32_t cmd, int len)
|
||||||
|
|
||||||
case 0x26: /* GAMSET */
|
case 0x26: /* GAMSET */
|
||||||
if (!s->pm) {
|
if (!s->pm) {
|
||||||
s->gamma = ffs(s->param[0] & 0xf) - 1;
|
s->gamma = ctz32(s->param[0] & 0xf);
|
||||||
|
if (s->gamma == 32) {
|
||||||
|
s->gamma = -1; /* XXX: should this be 0? */
|
||||||
|
}
|
||||||
} else if (s->pm < 0) {
|
} else if (s->pm < 0) {
|
||||||
s->pm = 1;
|
s->pm = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2004,8 +2004,7 @@ static void omap_mpuio_write(void *opaque, hwaddr addr,
|
||||||
case 0x04: /* OUTPUT_REG */
|
case 0x04: /* OUTPUT_REG */
|
||||||
diff = (s->outputs ^ value) & ~s->dir;
|
diff = (s->outputs ^ value) & ~s->dir;
|
||||||
s->outputs = value;
|
s->outputs = value;
|
||||||
while ((ln = ffs(diff))) {
|
while ((ln = ctz32(diff)) != 32) {
|
||||||
ln --;
|
|
||||||
if (s->handler[ln])
|
if (s->handler[ln])
|
||||||
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
||||||
diff &= ~(1 << ln);
|
diff &= ~(1 << ln);
|
||||||
|
@ -2017,8 +2016,7 @@ static void omap_mpuio_write(void *opaque, hwaddr addr,
|
||||||
s->dir = value;
|
s->dir = value;
|
||||||
|
|
||||||
value = s->outputs & ~s->dir;
|
value = s->outputs & ~s->dir;
|
||||||
while ((ln = ffs(diff))) {
|
while ((ln = ctz32(diff)) != 32) {
|
||||||
ln --;
|
|
||||||
if (s->handler[ln])
|
if (s->handler[ln])
|
||||||
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
||||||
diff &= ~(1 << ln);
|
diff &= ~(1 << ln);
|
||||||
|
|
|
@ -137,7 +137,7 @@ static void pxa2xx_gpio_handler_update(PXA2xxGPIOInfo *s) {
|
||||||
level = s->olevel[i] & s->dir[i];
|
level = s->olevel[i] & s->dir[i];
|
||||||
|
|
||||||
for (diff = s->prev_level[i] ^ level; diff; diff ^= 1 << bit) {
|
for (diff = s->prev_level[i] ^ level; diff; diff ^= 1 << bit) {
|
||||||
bit = ffs(diff) - 1;
|
bit = ctz32(diff);
|
||||||
line = bit + 32 * i;
|
line = bit + 32 * i;
|
||||||
qemu_set_irq(s->handler[line], (level >> bit) & 1);
|
qemu_set_irq(s->handler[line], (level >> bit) & 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -528,7 +528,7 @@ static void strongarm_gpio_handler_update(StrongARMGPIOInfo *s)
|
||||||
level = s->olevel & s->dir;
|
level = s->olevel & s->dir;
|
||||||
|
|
||||||
for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
|
for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
|
||||||
bit = ffs(diff) - 1;
|
bit = ctz32(diff);
|
||||||
qemu_set_irq(s->handler[bit], (level >> bit) & 1);
|
qemu_set_irq(s->handler[bit], (level >> bit) & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -745,7 +745,7 @@ static void strongarm_ppc_handler_update(StrongARMPPCInfo *s)
|
||||||
level = s->olevel & s->dir;
|
level = s->olevel & s->dir;
|
||||||
|
|
||||||
for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
|
for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
|
||||||
bit = ffs(diff) - 1;
|
bit = ctz32(diff);
|
||||||
qemu_set_irq(s->handler[bit], (level >> bit) & 1);
|
qemu_set_irq(s->handler[bit], (level >> bit) & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -621,7 +621,6 @@ static int m25p80_init(SSISlave *ss)
|
||||||
|
|
||||||
s->size = s->pi->sector_size * s->pi->n_sectors;
|
s->size = s->pi->sector_size * s->pi->n_sectors;
|
||||||
s->dirty_page = -1;
|
s->dirty_page = -1;
|
||||||
s->storage = blk_blockalign(s->blk, s->size);
|
|
||||||
|
|
||||||
/* FIXME use a qdev drive property instead of drive_get_next() */
|
/* FIXME use a qdev drive property instead of drive_get_next() */
|
||||||
dinfo = drive_get_next(IF_MTD);
|
dinfo = drive_get_next(IF_MTD);
|
||||||
|
@ -629,6 +628,9 @@ static int m25p80_init(SSISlave *ss)
|
||||||
if (dinfo) {
|
if (dinfo) {
|
||||||
DB_PRINT_L(0, "Binding to IF_MTD drive\n");
|
DB_PRINT_L(0, "Binding to IF_MTD drive\n");
|
||||||
s->blk = blk_by_legacy_dinfo(dinfo);
|
s->blk = blk_by_legacy_dinfo(dinfo);
|
||||||
|
blk_attach_dev_nofail(s->blk, s);
|
||||||
|
|
||||||
|
s->storage = blk_blockalign(s->blk, s->size);
|
||||||
|
|
||||||
/* FIXME: Move to late init */
|
/* FIXME: Move to late init */
|
||||||
if (blk_read(s->blk, 0, s->storage,
|
if (blk_read(s->blk, 0, s->storage,
|
||||||
|
@ -638,6 +640,7 @@ static int m25p80_init(SSISlave *ss)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DB_PRINT_L(0, "No BDRV - binding to RAM\n");
|
DB_PRINT_L(0, "No BDRV - binding to RAM\n");
|
||||||
|
s->storage = blk_blockalign(NULL, s->size);
|
||||||
memset(s->storage, 0xFF, s->size);
|
memset(s->storage, 0xFF, s->size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -707,7 +707,7 @@ static void sdp_service_record_build(struct sdp_service_record_s *record,
|
||||||
len += sdp_attr_max_size(&def->attributes[record->attributes ++].data,
|
len += sdp_attr_max_size(&def->attributes[record->attributes ++].data,
|
||||||
&record->uuids);
|
&record->uuids);
|
||||||
}
|
}
|
||||||
record->uuids = 1 << ffs(record->uuids - 1);
|
record->uuids = pow2ceil(record->uuids);
|
||||||
record->attribute_list =
|
record->attribute_list =
|
||||||
g_malloc0(record->attributes * sizeof(*record->attribute_list));
|
g_malloc0(record->attributes * sizeof(*record->attribute_list));
|
||||||
record->uuid =
|
record->uuid =
|
||||||
|
|
|
@ -814,12 +814,12 @@ static uint32_t find_free_port_id(VirtIOSerial *vser)
|
||||||
|
|
||||||
max_nr_ports = vser->serial.max_virtserial_ports;
|
max_nr_ports = vser->serial.max_virtserial_ports;
|
||||||
for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
|
for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
|
||||||
uint32_t map, bit;
|
uint32_t map, zeroes;
|
||||||
|
|
||||||
map = vser->ports_map[i];
|
map = vser->ports_map[i];
|
||||||
bit = ffs(~map);
|
zeroes = ctz32(~map);
|
||||||
if (bit) {
|
if (zeroes != 32) {
|
||||||
return (bit - 1) + i * 32;
|
return zeroes + i * 32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return VIRTIO_CONSOLE_BAD_ID;
|
return VIRTIO_CONSOLE_BAD_ID;
|
||||||
|
|
|
@ -171,7 +171,7 @@ static void tc6393xb_gpio_handler_update(TC6393xbState *s)
|
||||||
level = s->gpio_level & s->gpio_dir;
|
level = s->gpio_level & s->gpio_dir;
|
||||||
|
|
||||||
for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
|
for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
|
||||||
bit = ffs(diff) - 1;
|
bit = ctz32(diff);
|
||||||
qemu_set_irq(s->handler[bit], (level >> bit) & 1);
|
qemu_set_irq(s->handler[bit], (level >> bit) & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ static int max7310_tx(I2CSlave *i2c, uint8_t data)
|
||||||
case 0x01: /* Output port */
|
case 0x01: /* Output port */
|
||||||
for (diff = (data ^ s->level) & ~s->direction; diff;
|
for (diff = (data ^ s->level) & ~s->direction; diff;
|
||||||
diff &= ~(1 << line)) {
|
diff &= ~(1 << line)) {
|
||||||
line = ffs(diff) - 1;
|
line = ctz32(diff);
|
||||||
if (s->handler[line])
|
if (s->handler[line])
|
||||||
qemu_set_irq(s->handler[line], (data >> line) & 1);
|
qemu_set_irq(s->handler[line], (data >> line) & 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,8 +125,7 @@ static void omap_gpio_write(void *opaque, hwaddr addr,
|
||||||
case 0x04: /* DATA_OUTPUT */
|
case 0x04: /* DATA_OUTPUT */
|
||||||
diff = (s->outputs ^ value) & ~s->dir;
|
diff = (s->outputs ^ value) & ~s->dir;
|
||||||
s->outputs = value;
|
s->outputs = value;
|
||||||
while ((ln = ffs(diff))) {
|
while ((ln = ctz32(diff)) != 32) {
|
||||||
ln --;
|
|
||||||
if (s->handler[ln])
|
if (s->handler[ln])
|
||||||
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
||||||
diff &= ~(1 << ln);
|
diff &= ~(1 << ln);
|
||||||
|
@ -138,8 +137,7 @@ static void omap_gpio_write(void *opaque, hwaddr addr,
|
||||||
s->dir = value;
|
s->dir = value;
|
||||||
|
|
||||||
value = s->outputs & ~s->dir;
|
value = s->outputs & ~s->dir;
|
||||||
while ((ln = ffs(diff))) {
|
while ((ln = ctz32(diff)) != 32) {
|
||||||
ln --;
|
|
||||||
if (s->handler[ln])
|
if (s->handler[ln])
|
||||||
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
||||||
diff &= ~(1 << ln);
|
diff &= ~(1 << ln);
|
||||||
|
@ -253,8 +251,7 @@ static inline void omap2_gpio_module_out_update(struct omap2_gpio_s *s,
|
||||||
|
|
||||||
s->outputs ^= diff;
|
s->outputs ^= diff;
|
||||||
diff &= ~s->dir;
|
diff &= ~s->dir;
|
||||||
while ((ln = ffs(diff))) {
|
while ((ln = ctz32(diff)) != 32) {
|
||||||
ln --;
|
|
||||||
qemu_set_irq(s->handler[ln], (s->outputs >> ln) & 1);
|
qemu_set_irq(s->handler[ln], (s->outputs >> ln) & 1);
|
||||||
diff &= ~(1 << ln);
|
diff &= ~(1 << ln);
|
||||||
}
|
}
|
||||||
|
@ -442,8 +439,8 @@ static void omap2_gpio_module_write(void *opaque, hwaddr addr,
|
||||||
s->dir = value;
|
s->dir = value;
|
||||||
|
|
||||||
value = s->outputs & ~s->dir;
|
value = s->outputs & ~s->dir;
|
||||||
while ((ln = ffs(diff))) {
|
while ((ln = ctz32(diff)) != 32) {
|
||||||
diff &= ~(1 <<-- ln);
|
diff &= ~(1 << ln);
|
||||||
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
qemu_set_irq(s->handler[ln], (value >> ln) & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ static inline void scoop_gpio_handler_update(ScoopInfo *s) {
|
||||||
level = s->gpio_level & s->gpio_dir;
|
level = s->gpio_level & s->gpio_dir;
|
||||||
|
|
||||||
for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
|
for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
|
||||||
bit = ffs(diff) - 1;
|
bit = ctz32(diff);
|
||||||
qemu_set_irq(s->handler[bit], (level >> bit) & 1);
|
qemu_set_irq(s->handler[bit], (level >> bit) & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,9 +171,13 @@ static uint32_t omap_i2c_read(void *opaque, hwaddr addr)
|
||||||
case 0x0c: /* I2C_IV */
|
case 0x0c: /* I2C_IV */
|
||||||
if (s->revision >= OMAP2_INTR_REV)
|
if (s->revision >= OMAP2_INTR_REV)
|
||||||
break;
|
break;
|
||||||
ret = ffs(s->stat & s->mask);
|
ret = ctz32(s->stat & s->mask);
|
||||||
if (ret)
|
if (ret != 32) {
|
||||||
s->stat ^= 1 << (ret - 1);
|
s->stat ^= 1 << ret;
|
||||||
|
ret++;
|
||||||
|
} else {
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
omap_i2c_interrupts_update(s);
|
omap_i2c_interrupts_update(s);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
static void aw_a10_pic_update(AwA10PICState *s)
|
static void aw_a10_pic_update(AwA10PICState *s)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
int irq = 0, fiq = 0, pending;
|
int irq = 0, fiq = 0, zeroes;
|
||||||
|
|
||||||
s->vector = 0;
|
s->vector = 0;
|
||||||
|
|
||||||
|
@ -32,9 +32,9 @@ static void aw_a10_pic_update(AwA10PICState *s)
|
||||||
fiq |= s->select[i] & s->irq_pending[i] & ~s->mask[i];
|
fiq |= s->select[i] & s->irq_pending[i] & ~s->mask[i];
|
||||||
|
|
||||||
if (!s->vector) {
|
if (!s->vector) {
|
||||||
pending = ffs(s->irq_pending[i] & ~s->mask[i]);
|
zeroes = ctz32(s->irq_pending[i] & ~s->mask[i]);
|
||||||
if (pending) {
|
if (zeroes != 32) {
|
||||||
s->vector = (i * 32 + pending - 1) * 4;
|
s->vector = (i * 32 + zeroes) * 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ struct omap_intr_handler_s {
|
||||||
|
|
||||||
static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq)
|
static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq)
|
||||||
{
|
{
|
||||||
int i, j, sir_intr, p_intr, p, f;
|
int i, j, sir_intr, p_intr, p;
|
||||||
uint32_t level;
|
uint32_t level;
|
||||||
sir_intr = 0;
|
sir_intr = 0;
|
||||||
p_intr = 255;
|
p_intr = 255;
|
||||||
|
@ -72,14 +72,15 @@ static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq)
|
||||||
for (j = 0; j < s->nbanks; ++j) {
|
for (j = 0; j < s->nbanks; ++j) {
|
||||||
level = s->bank[j].irqs & ~s->bank[j].mask &
|
level = s->bank[j].irqs & ~s->bank[j].mask &
|
||||||
(is_fiq ? s->bank[j].fiq : ~s->bank[j].fiq);
|
(is_fiq ? s->bank[j].fiq : ~s->bank[j].fiq);
|
||||||
for (f = ffs(level), i = f - 1, level >>= f - 1; f; i += f,
|
|
||||||
level >>= f) {
|
while (level != 0) {
|
||||||
|
i = ctz32(level);
|
||||||
p = s->bank[j].priority[i];
|
p = s->bank[j].priority[i];
|
||||||
if (p <= p_intr) {
|
if (p <= p_intr) {
|
||||||
p_intr = p;
|
p_intr = p;
|
||||||
sir_intr = 32 * j + i;
|
sir_intr = 32 * j + i;
|
||||||
}
|
}
|
||||||
f = ffs(level >> 1);
|
level &= level - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s->sir_intr[is_fiq] = sir_intr;
|
s->sir_intr[is_fiq] = sir_intr;
|
||||||
|
|
|
@ -427,7 +427,7 @@ static uint32_t bonito_sbridge_pciaddr(void *opaque, hwaddr addr)
|
||||||
cfgaddr |= (s->regs[BONITO_PCIMAP_CFG] & 0xffff) << 16;
|
cfgaddr |= (s->regs[BONITO_PCIMAP_CFG] & 0xffff) << 16;
|
||||||
|
|
||||||
idsel = (cfgaddr & BONITO_PCICONF_IDSEL_MASK) >> BONITO_PCICONF_IDSEL_OFFSET;
|
idsel = (cfgaddr & BONITO_PCICONF_IDSEL_MASK) >> BONITO_PCICONF_IDSEL_OFFSET;
|
||||||
devno = ffs(idsel) - 1;
|
devno = ctz32(idsel);
|
||||||
funno = (cfgaddr & BONITO_PCICONF_FUN_MASK) >> BONITO_PCICONF_FUN_OFFSET;
|
funno = (cfgaddr & BONITO_PCICONF_FUN_MASK) >> BONITO_PCICONF_FUN_OFFSET;
|
||||||
regno = (cfgaddr & BONITO_PCICONF_REG_MASK) >> BONITO_PCICONF_REG_OFFSET;
|
regno = (cfgaddr & BONITO_PCICONF_REG_MASK) >> BONITO_PCICONF_REG_OFFSET;
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,10 @@ static uint32_t unin_get_config_reg(uint32_t reg, uint32_t addr)
|
||||||
uint32_t slot, func;
|
uint32_t slot, func;
|
||||||
|
|
||||||
/* Grab CFA0 style values */
|
/* Grab CFA0 style values */
|
||||||
slot = ffs(reg & 0xfffff800) - 1;
|
slot = ctz32(reg & 0xfffff800);
|
||||||
|
if (slot == 32) {
|
||||||
|
slot = -1; /* XXX: should this be 0? */
|
||||||
|
}
|
||||||
func = (reg >> 8) & 7;
|
func = (reg >> 8) & 7;
|
||||||
|
|
||||||
/* ... and then convert them to x86 format */
|
/* ... and then convert them to x86 format */
|
||||||
|
|
12
hw/pci/msi.c
12
hw/pci/msi.c
|
@ -72,7 +72,7 @@ static inline uint8_t msi_cap_sizeof(uint16_t flags)
|
||||||
static inline unsigned int msi_nr_vectors(uint16_t flags)
|
static inline unsigned int msi_nr_vectors(uint16_t flags)
|
||||||
{
|
{
|
||||||
return 1U <<
|
return 1U <<
|
||||||
((flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1));
|
((flags & PCI_MSI_FLAGS_QSIZE) >> ctz32(PCI_MSI_FLAGS_QSIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint8_t msi_flags_off(const PCIDevice* dev)
|
static inline uint8_t msi_flags_off(const PCIDevice* dev)
|
||||||
|
@ -175,9 +175,9 @@ int msi_init(struct PCIDevice *dev, uint8_t offset,
|
||||||
assert(nr_vectors > 0);
|
assert(nr_vectors > 0);
|
||||||
assert(nr_vectors <= PCI_MSI_VECTORS_MAX);
|
assert(nr_vectors <= PCI_MSI_VECTORS_MAX);
|
||||||
/* the nr of MSI vectors is up to 32 */
|
/* the nr of MSI vectors is up to 32 */
|
||||||
vectors_order = ffs(nr_vectors) - 1;
|
vectors_order = ctz32(nr_vectors);
|
||||||
|
|
||||||
flags = vectors_order << (ffs(PCI_MSI_FLAGS_QMASK) - 1);
|
flags = vectors_order << ctz32(PCI_MSI_FLAGS_QMASK);
|
||||||
if (msi64bit) {
|
if (msi64bit) {
|
||||||
flags |= PCI_MSI_FLAGS_64BIT;
|
flags |= PCI_MSI_FLAGS_64BIT;
|
||||||
}
|
}
|
||||||
|
@ -355,12 +355,12 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
|
||||||
* just don't crash the host
|
* just don't crash the host
|
||||||
*/
|
*/
|
||||||
log_num_vecs =
|
log_num_vecs =
|
||||||
(flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1);
|
(flags & PCI_MSI_FLAGS_QSIZE) >> ctz32(PCI_MSI_FLAGS_QSIZE);
|
||||||
log_max_vecs =
|
log_max_vecs =
|
||||||
(flags & PCI_MSI_FLAGS_QMASK) >> (ffs(PCI_MSI_FLAGS_QMASK) - 1);
|
(flags & PCI_MSI_FLAGS_QMASK) >> ctz32(PCI_MSI_FLAGS_QMASK);
|
||||||
if (log_num_vecs > log_max_vecs) {
|
if (log_num_vecs > log_max_vecs) {
|
||||||
flags &= ~PCI_MSI_FLAGS_QSIZE;
|
flags &= ~PCI_MSI_FLAGS_QSIZE;
|
||||||
flags |= log_max_vecs << (ffs(PCI_MSI_FLAGS_QSIZE) - 1);
|
flags |= log_max_vecs << ctz32(PCI_MSI_FLAGS_QSIZE);
|
||||||
pci_set_word(dev->config + msi_flags_off(dev), flags);
|
pci_set_word(dev->config + msi_flags_off(dev), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -410,7 +410,7 @@ static void pcie_aer_msg(PCIDevice *dev, const PCIEAERMsg *msg)
|
||||||
static void pcie_aer_update_log(PCIDevice *dev, const PCIEAERErr *err)
|
static void pcie_aer_update_log(PCIDevice *dev, const PCIEAERErr *err)
|
||||||
{
|
{
|
||||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||||
uint8_t first_bit = ffs(err->status) - 1;
|
uint8_t first_bit = ctz32(err->status);
|
||||||
uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
|
uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
/* Same slot state masks are used for command and status registers */
|
/* Same slot state masks are used for command and status registers */
|
||||||
#define SHPC_SLOT_STATE_MASK 0x03
|
#define SHPC_SLOT_STATE_MASK 0x03
|
||||||
#define SHPC_SLOT_STATE_SHIFT \
|
#define SHPC_SLOT_STATE_SHIFT \
|
||||||
(ffs(SHPC_SLOT_STATE_MASK) - 1)
|
ctz32(SHPC_SLOT_STATE_MASK)
|
||||||
|
|
||||||
#define SHPC_STATE_NO 0x0
|
#define SHPC_STATE_NO 0x0
|
||||||
#define SHPC_STATE_PWRONLY 0x1
|
#define SHPC_STATE_PWRONLY 0x1
|
||||||
|
@ -70,10 +70,10 @@
|
||||||
|
|
||||||
#define SHPC_SLOT_PWR_LED_MASK 0xC
|
#define SHPC_SLOT_PWR_LED_MASK 0xC
|
||||||
#define SHPC_SLOT_PWR_LED_SHIFT \
|
#define SHPC_SLOT_PWR_LED_SHIFT \
|
||||||
(ffs(SHPC_SLOT_PWR_LED_MASK) - 1)
|
ctz32(SHPC_SLOT_PWR_LED_MASK)
|
||||||
#define SHPC_SLOT_ATTN_LED_MASK 0x30
|
#define SHPC_SLOT_ATTN_LED_MASK 0x30
|
||||||
#define SHPC_SLOT_ATTN_LED_SHIFT \
|
#define SHPC_SLOT_ATTN_LED_SHIFT \
|
||||||
(ffs(SHPC_SLOT_ATTN_LED_MASK) - 1)
|
ctz32(SHPC_SLOT_ATTN_LED_MASK)
|
||||||
|
|
||||||
#define SHPC_LED_NO 0x0
|
#define SHPC_LED_NO 0x0
|
||||||
#define SHPC_LED_ON 0x1
|
#define SHPC_LED_ON 0x1
|
||||||
|
@ -136,7 +136,7 @@ static int roundup_pow_of_two(int x)
|
||||||
static uint16_t shpc_get_status(SHPCDevice *shpc, int slot, uint16_t msk)
|
static uint16_t shpc_get_status(SHPCDevice *shpc, int slot, uint16_t msk)
|
||||||
{
|
{
|
||||||
uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
|
uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
|
||||||
return (pci_get_word(status) & msk) >> (ffs(msk) - 1);
|
return (pci_get_word(status) & msk) >> ctz32(msk);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void shpc_set_status(SHPCDevice *shpc,
|
static void shpc_set_status(SHPCDevice *shpc,
|
||||||
|
@ -144,7 +144,7 @@ static void shpc_set_status(SHPCDevice *shpc,
|
||||||
{
|
{
|
||||||
uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
|
uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
|
||||||
pci_word_test_and_clear_mask(status, msk);
|
pci_word_test_and_clear_mask(status, msk);
|
||||||
pci_word_test_and_set_mask(status, value << (ffs(msk) - 1));
|
pci_word_test_and_set_mask(status, value << ctz32(msk));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void shpc_interrupt_update(PCIDevice *d)
|
static void shpc_interrupt_update(PCIDevice *d)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include "qemu/error-report.h"
|
#include "qemu/error-report.h"
|
||||||
|
|
||||||
#define SLOTID_CAP_LENGTH 4
|
#define SLOTID_CAP_LENGTH 4
|
||||||
#define SLOTID_NSLOTS_SHIFT (ffs(PCI_SID_ESR_NSLOTS) - 1)
|
#define SLOTID_NSLOTS_SHIFT ctz32(PCI_SID_ESR_NSLOTS)
|
||||||
|
|
||||||
int slotid_cap_init(PCIDevice *d, int nslots,
|
int slotid_cap_init(PCIDevice *d, int nslots,
|
||||||
uint8_t chassis,
|
uint8_t chassis,
|
||||||
|
|
|
@ -74,7 +74,7 @@ static void spin_reset(void *opaque)
|
||||||
/* Create -kernel TLB entries for BookE, linearly spanning 256MB. */
|
/* Create -kernel TLB entries for BookE, linearly spanning 256MB. */
|
||||||
static inline hwaddr booke206_page_size_to_tlb(uint64_t size)
|
static inline hwaddr booke206_page_size_to_tlb(uint64_t size)
|
||||||
{
|
{
|
||||||
return (ffs(size >> 10) - 1) >> 1;
|
return ctz32(size >> 10) >> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mmubooke_create_initial_mapping(CPUPPCState *env,
|
static void mmubooke_create_initial_mapping(CPUPPCState *env,
|
||||||
|
|
|
@ -804,7 +804,7 @@ static int megasas_ctrl_get_info(MegasasState *s, MegasasCmd *cmd)
|
||||||
MFI_INFO_LDOPS_READ_POLICY);
|
MFI_INFO_LDOPS_READ_POLICY);
|
||||||
info.max_strips_per_io = cpu_to_le16(s->fw_sge);
|
info.max_strips_per_io = cpu_to_le16(s->fw_sge);
|
||||||
info.stripe_sz_ops.min = 3;
|
info.stripe_sz_ops.min = 3;
|
||||||
info.stripe_sz_ops.max = ffs(MEGASAS_MAX_SECTORS + 1) - 1;
|
info.stripe_sz_ops.max = ctz32(MEGASAS_MAX_SECTORS + 1);
|
||||||
info.properties.pred_fail_poll_interval = cpu_to_le16(300);
|
info.properties.pred_fail_poll_interval = cpu_to_le16(300);
|
||||||
info.properties.intr_throttle_cnt = cpu_to_le16(16);
|
info.properties.intr_throttle_cnt = cpu_to_le16(16);
|
||||||
info.properties.intr_throttle_timeout = cpu_to_le16(50);
|
info.properties.intr_throttle_timeout = cpu_to_le16(50);
|
||||||
|
|
|
@ -796,8 +796,9 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
|
||||||
sd->vhs = 0;
|
sd->vhs = 0;
|
||||||
|
|
||||||
/* No response if not exactly one VHS bit is set. */
|
/* No response if not exactly one VHS bit is set. */
|
||||||
if (!(req.arg >> 8) || (req.arg >> ffs(req.arg & ~0xff)))
|
if (!(req.arg >> 8) || (req.arg >> (ctz32(req.arg & ~0xff) + 1))) {
|
||||||
return sd->spi ? sd_r7 : sd_r0;
|
return sd->spi ? sd_r7 : sd_r0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Accept. */
|
/* Accept. */
|
||||||
sd->vhs = req.arg;
|
sd->vhs = req.arg;
|
||||||
|
|
|
@ -82,9 +82,6 @@ struct AioContext {
|
||||||
/* Used for aio_notify. */
|
/* Used for aio_notify. */
|
||||||
EventNotifier notifier;
|
EventNotifier notifier;
|
||||||
|
|
||||||
/* GPollFDs for aio_poll() */
|
|
||||||
GArray *pollfds;
|
|
||||||
|
|
||||||
/* Thread pool for performing work and receiving completion callbacks */
|
/* Thread pool for performing work and receiving completion callbacks */
|
||||||
struct ThreadPool *thread_pool;
|
struct ThreadPool *thread_pool;
|
||||||
|
|
||||||
|
@ -121,13 +118,14 @@ void aio_context_ref(AioContext *ctx);
|
||||||
void aio_context_unref(AioContext *ctx);
|
void aio_context_unref(AioContext *ctx);
|
||||||
|
|
||||||
/* Take ownership of the AioContext. If the AioContext will be shared between
|
/* Take ownership of the AioContext. If the AioContext will be shared between
|
||||||
* threads, a thread must have ownership when calling aio_poll().
|
* threads, and a thread does not want to be interrupted, it will have to
|
||||||
|
* take ownership around calls to aio_poll(). Otherwise, aio_poll()
|
||||||
|
* automatically takes care of calling aio_context_acquire and
|
||||||
|
* aio_context_release.
|
||||||
*
|
*
|
||||||
* Note that multiple threads calling aio_poll() means timers, BHs, and
|
* Access to timers and BHs from a thread that has not acquired AioContext
|
||||||
* callbacks may be invoked from a different thread than they were registered
|
* is possible. Access to callbacks for now must be done while the AioContext
|
||||||
* from. Therefore, code must use AioContext acquire/release or use
|
* is owned by the thread (FIXME).
|
||||||
* fine-grained synchronization to protect shared state if other threads will
|
|
||||||
* be accessing it simultaneously.
|
|
||||||
*/
|
*/
|
||||||
void aio_context_acquire(AioContext *ctx);
|
void aio_context_acquire(AioContext *ctx);
|
||||||
|
|
||||||
|
|
|
@ -382,7 +382,7 @@ void bdrv_lock_medium(BlockDriverState *bs, bool locked);
|
||||||
void bdrv_eject(BlockDriverState *bs, bool eject_flag);
|
void bdrv_eject(BlockDriverState *bs, bool eject_flag);
|
||||||
const char *bdrv_get_format_name(BlockDriverState *bs);
|
const char *bdrv_get_format_name(BlockDriverState *bs);
|
||||||
BlockDriverState *bdrv_find_node(const char *node_name);
|
BlockDriverState *bdrv_find_node(const char *node_name);
|
||||||
BlockDeviceInfoList *bdrv_named_nodes_list(void);
|
BlockDeviceInfoList *bdrv_named_nodes_list(Error **errp);
|
||||||
BlockDriverState *bdrv_lookup_bs(const char *device,
|
BlockDriverState *bdrv_lookup_bs(const char *device,
|
||||||
const char *node_name,
|
const char *node_name,
|
||||||
Error **errp);
|
Error **errp);
|
||||||
|
@ -398,6 +398,7 @@ void bdrv_iterate_format(void (*it)(void *opaque, const char *name),
|
||||||
void *opaque);
|
void *opaque);
|
||||||
const char *bdrv_get_node_name(const BlockDriverState *bs);
|
const char *bdrv_get_node_name(const BlockDriverState *bs);
|
||||||
const char *bdrv_get_device_name(const BlockDriverState *bs);
|
const char *bdrv_get_device_name(const BlockDriverState *bs);
|
||||||
|
const char *bdrv_get_device_or_node_name(const BlockDriverState *bs);
|
||||||
int bdrv_get_flags(BlockDriverState *bs);
|
int bdrv_get_flags(BlockDriverState *bs);
|
||||||
int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num,
|
int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num,
|
||||||
const uint8_t *buf, int nb_sectors);
|
const uint8_t *buf, int nb_sectors);
|
||||||
|
@ -449,18 +450,39 @@ bool bdrv_qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov);
|
||||||
|
|
||||||
struct HBitmapIter;
|
struct HBitmapIter;
|
||||||
typedef struct BdrvDirtyBitmap BdrvDirtyBitmap;
|
typedef struct BdrvDirtyBitmap BdrvDirtyBitmap;
|
||||||
BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs, int granularity,
|
BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
|
||||||
|
uint32_t granularity,
|
||||||
|
const char *name,
|
||||||
Error **errp);
|
Error **errp);
|
||||||
|
int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
|
||||||
|
BdrvDirtyBitmap *bitmap,
|
||||||
|
Error **errp);
|
||||||
|
BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
|
||||||
|
BdrvDirtyBitmap *bitmap,
|
||||||
|
Error **errp);
|
||||||
|
BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
|
||||||
|
BdrvDirtyBitmap *bitmap,
|
||||||
|
Error **errp);
|
||||||
|
BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
|
||||||
|
const char *name);
|
||||||
|
void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap);
|
||||||
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
|
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
|
||||||
|
void bdrv_disable_dirty_bitmap(BdrvDirtyBitmap *bitmap);
|
||||||
|
void bdrv_enable_dirty_bitmap(BdrvDirtyBitmap *bitmap);
|
||||||
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
|
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
|
||||||
|
uint32_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs);
|
||||||
|
uint32_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap);
|
||||||
|
bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap);
|
||||||
|
bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap);
|
||||||
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
|
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
|
||||||
void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
void bdrv_set_dirty_bitmap(BdrvDirtyBitmap *bitmap,
|
||||||
int64_t cur_sector, int nr_sectors);
|
int64_t cur_sector, int nr_sectors);
|
||||||
void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
void bdrv_reset_dirty_bitmap(BdrvDirtyBitmap *bitmap,
|
||||||
int64_t cur_sector, int nr_sectors);
|
int64_t cur_sector, int nr_sectors);
|
||||||
void bdrv_dirty_iter_init(BlockDriverState *bs,
|
void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap);
|
||||||
BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
|
void bdrv_dirty_iter_init(BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
|
||||||
int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
|
void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
|
||||||
|
int64_t bdrv_get_dirty_count(BdrvDirtyBitmap *bitmap);
|
||||||
|
|
||||||
void bdrv_enable_copy_on_read(BlockDriverState *bs);
|
void bdrv_enable_copy_on_read(BlockDriverState *bs);
|
||||||
void bdrv_disable_copy_on_read(BlockDriverState *bs);
|
void bdrv_disable_copy_on_read(BlockDriverState *bs);
|
||||||
|
|
|
@ -439,6 +439,14 @@ extern BlockDriver bdrv_file;
|
||||||
extern BlockDriver bdrv_raw;
|
extern BlockDriver bdrv_raw;
|
||||||
extern BlockDriver bdrv_qcow2;
|
extern BlockDriver bdrv_qcow2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bdrv_setup_io_funcs:
|
||||||
|
*
|
||||||
|
* Prepare a #BlockDriver for I/O request processing by populating
|
||||||
|
* unimplemented coroutine and AIO interfaces with generic wrapper functions
|
||||||
|
* that fall back to implemented interfaces.
|
||||||
|
*/
|
||||||
|
void bdrv_setup_io_funcs(BlockDriver *bdrv);
|
||||||
|
|
||||||
int get_tmp_filename(char *filename, int size);
|
int get_tmp_filename(char *filename, int size);
|
||||||
BlockDriver *bdrv_probe_all(const uint8_t *buf, int buf_size,
|
BlockDriver *bdrv_probe_all(const uint8_t *buf, int buf_size,
|
||||||
|
@ -590,7 +598,7 @@ void commit_active_start(BlockDriverState *bs, BlockDriverState *base,
|
||||||
*/
|
*/
|
||||||
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
const char *replaces,
|
const char *replaces,
|
||||||
int64_t speed, int64_t granularity, int64_t buf_size,
|
int64_t speed, uint32_t granularity, int64_t buf_size,
|
||||||
MirrorSyncMode mode, BlockdevOnError on_source_error,
|
MirrorSyncMode mode, BlockdevOnError on_source_error,
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
BlockCompletionFunc *cb,
|
BlockCompletionFunc *cb,
|
||||||
|
@ -602,6 +610,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
* @target: Block device to write to.
|
* @target: Block device to write to.
|
||||||
* @speed: The maximum speed, in bytes per second, or 0 for unlimited.
|
* @speed: The maximum speed, in bytes per second, or 0 for unlimited.
|
||||||
* @sync_mode: What parts of the disk image should be copied to the destination.
|
* @sync_mode: What parts of the disk image should be copied to the destination.
|
||||||
|
* @sync_bitmap: The dirty bitmap if sync_mode is MIRROR_SYNC_MODE_DIRTY_BITMAP.
|
||||||
* @on_source_error: The action to take upon error reading from the source.
|
* @on_source_error: The action to take upon error reading from the source.
|
||||||
* @on_target_error: The action to take upon error writing to the target.
|
* @on_target_error: The action to take upon error writing to the target.
|
||||||
* @cb: Completion function for the job.
|
* @cb: Completion function for the job.
|
||||||
|
@ -612,6 +621,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
*/
|
*/
|
||||||
void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
int64_t speed, MirrorSyncMode sync_mode,
|
int64_t speed, MirrorSyncMode sync_mode,
|
||||||
|
BdrvDirtyBitmap *sync_bitmap,
|
||||||
BlockdevOnError on_source_error,
|
BlockdevOnError on_source_error,
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
BlockCompletionFunc *cb, void *opaque,
|
BlockCompletionFunc *cb, void *opaque,
|
||||||
|
@ -624,4 +634,8 @@ bool blk_dev_is_tray_open(BlockBackend *blk);
|
||||||
bool blk_dev_is_medium_locked(BlockBackend *blk);
|
bool blk_dev_is_medium_locked(BlockBackend *blk);
|
||||||
void blk_dev_resize_cb(BlockBackend *blk);
|
void blk_dev_resize_cb(BlockBackend *blk);
|
||||||
|
|
||||||
|
void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
|
||||||
|
void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||||
|
int nr_sectors);
|
||||||
|
|
||||||
#endif /* BLOCK_INT_H */
|
#endif /* BLOCK_INT_H */
|
||||||
|
|
|
@ -79,10 +79,16 @@ struct BlockJob {
|
||||||
bool cancelled;
|
bool cancelled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to true if the job is either paused, or will pause itself
|
* Counter for pause request. If non-zero, the block job is either paused,
|
||||||
* as soon as possible (if busy == true).
|
* or if busy == true will pause itself as soon as possible.
|
||||||
*/
|
*/
|
||||||
bool paused;
|
int pause_count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true if the job is paused by user. Can be unpaused with the
|
||||||
|
* block-job-resume QMP command.
|
||||||
|
*/
|
||||||
|
bool user_paused;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to false by the job while it is in a quiescent state, where
|
* Set to false by the job while it is in a quiescent state, where
|
||||||
|
@ -225,10 +231,18 @@ void block_job_pause(BlockJob *job);
|
||||||
* block_job_resume:
|
* block_job_resume:
|
||||||
* @job: The job to be resumed.
|
* @job: The job to be resumed.
|
||||||
*
|
*
|
||||||
* Resume the specified job.
|
* Resume the specified job. Must be paired with a preceding block_job_pause.
|
||||||
*/
|
*/
|
||||||
void block_job_resume(BlockJob *job);
|
void block_job_resume(BlockJob *job);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* block_job_enter:
|
||||||
|
* @job: The job to enter.
|
||||||
|
*
|
||||||
|
* Continue the specified job by entering the coroutine.
|
||||||
|
*/
|
||||||
|
void block_job_enter(BlockJob *job);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* block_job_event_cancelled:
|
* block_job_event_cancelled:
|
||||||
* @job: The job whose information is requested.
|
* @job: The job whose information is requested.
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
#include "block/block.h"
|
#include "block/block.h"
|
||||||
#include "block/snapshot.h"
|
#include "block/snapshot.h"
|
||||||
|
|
||||||
BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs);
|
BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs, Error **errp);
|
||||||
int bdrv_query_snapshot_info_list(BlockDriverState *bs,
|
int bdrv_query_snapshot_info_list(BlockDriverState *bs,
|
||||||
SnapshotInfoList **p_list,
|
SnapshotInfoList **p_list,
|
||||||
Error **errp);
|
Error **errp);
|
||||||
|
|
|
@ -568,7 +568,7 @@ static inline void
|
||||||
pci_set_byte_by_mask(uint8_t *config, uint8_t mask, uint8_t reg)
|
pci_set_byte_by_mask(uint8_t *config, uint8_t mask, uint8_t reg)
|
||||||
{
|
{
|
||||||
uint8_t val = pci_get_byte(config);
|
uint8_t val = pci_get_byte(config);
|
||||||
uint8_t rval = reg << (ffs(mask) - 1);
|
uint8_t rval = reg << ctz32(mask);
|
||||||
pci_set_byte(config, (~mask & val) | (mask & rval));
|
pci_set_byte(config, (~mask & val) | (mask & rval));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,14 +576,14 @@ static inline uint8_t
|
||||||
pci_get_byte_by_mask(uint8_t *config, uint8_t mask)
|
pci_get_byte_by_mask(uint8_t *config, uint8_t mask)
|
||||||
{
|
{
|
||||||
uint8_t val = pci_get_byte(config);
|
uint8_t val = pci_get_byte(config);
|
||||||
return (val & mask) >> (ffs(mask) - 1);
|
return (val & mask) >> ctz32(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
pci_set_word_by_mask(uint8_t *config, uint16_t mask, uint16_t reg)
|
pci_set_word_by_mask(uint8_t *config, uint16_t mask, uint16_t reg)
|
||||||
{
|
{
|
||||||
uint16_t val = pci_get_word(config);
|
uint16_t val = pci_get_word(config);
|
||||||
uint16_t rval = reg << (ffs(mask) - 1);
|
uint16_t rval = reg << ctz32(mask);
|
||||||
pci_set_word(config, (~mask & val) | (mask & rval));
|
pci_set_word(config, (~mask & val) | (mask & rval));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,14 +591,14 @@ static inline uint16_t
|
||||||
pci_get_word_by_mask(uint8_t *config, uint16_t mask)
|
pci_get_word_by_mask(uint8_t *config, uint16_t mask)
|
||||||
{
|
{
|
||||||
uint16_t val = pci_get_word(config);
|
uint16_t val = pci_get_word(config);
|
||||||
return (val & mask) >> (ffs(mask) - 1);
|
return (val & mask) >> ctz32(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
pci_set_long_by_mask(uint8_t *config, uint32_t mask, uint32_t reg)
|
pci_set_long_by_mask(uint8_t *config, uint32_t mask, uint32_t reg)
|
||||||
{
|
{
|
||||||
uint32_t val = pci_get_long(config);
|
uint32_t val = pci_get_long(config);
|
||||||
uint32_t rval = reg << (ffs(mask) - 1);
|
uint32_t rval = reg << ctz32(mask);
|
||||||
pci_set_long(config, (~mask & val) | (mask & rval));
|
pci_set_long(config, (~mask & val) | (mask & rval));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,14 +606,14 @@ static inline uint32_t
|
||||||
pci_get_long_by_mask(uint8_t *config, uint32_t mask)
|
pci_get_long_by_mask(uint8_t *config, uint32_t mask)
|
||||||
{
|
{
|
||||||
uint32_t val = pci_get_long(config);
|
uint32_t val = pci_get_long(config);
|
||||||
return (val & mask) >> (ffs(mask) - 1);
|
return (val & mask) >> ctz32(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
pci_set_quad_by_mask(uint8_t *config, uint64_t mask, uint64_t reg)
|
pci_set_quad_by_mask(uint8_t *config, uint64_t mask, uint64_t reg)
|
||||||
{
|
{
|
||||||
uint64_t val = pci_get_quad(config);
|
uint64_t val = pci_get_quad(config);
|
||||||
uint64_t rval = reg << (ffs(mask) - 1);
|
uint64_t rval = reg << ctz32(mask);
|
||||||
pci_set_quad(config, (~mask & val) | (mask & rval));
|
pci_set_quad(config, (~mask & val) | (mask & rval));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,7 +621,7 @@ static inline uint64_t
|
||||||
pci_get_quad_by_mask(uint8_t *config, uint64_t mask)
|
pci_get_quad_by_mask(uint8_t *config, uint64_t mask)
|
||||||
{
|
{
|
||||||
uint64_t val = pci_get_quad(config);
|
uint64_t val = pci_get_quad(config);
|
||||||
return (val & mask) >> (ffs(mask) - 1);
|
return (val & mask) >> ctz32(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
PCIDevice *pci_create_multifunction(PCIBus *bus, int devfn, bool multifunction,
|
PCIDevice *pci_create_multifunction(PCIBus *bus, int devfn, bool multifunction,
|
||||||
|
|
|
@ -27,34 +27,34 @@
|
||||||
|
|
||||||
/* PCI_EXP_FLAGS */
|
/* PCI_EXP_FLAGS */
|
||||||
#define PCI_EXP_FLAGS_VER2 2 /* for now, supports only ver. 2 */
|
#define PCI_EXP_FLAGS_VER2 2 /* for now, supports only ver. 2 */
|
||||||
#define PCI_EXP_FLAGS_IRQ_SHIFT (ffs(PCI_EXP_FLAGS_IRQ) - 1)
|
#define PCI_EXP_FLAGS_IRQ_SHIFT ctz32(PCI_EXP_FLAGS_IRQ)
|
||||||
#define PCI_EXP_FLAGS_TYPE_SHIFT (ffs(PCI_EXP_FLAGS_TYPE) - 1)
|
#define PCI_EXP_FLAGS_TYPE_SHIFT ctz32(PCI_EXP_FLAGS_TYPE)
|
||||||
|
|
||||||
|
|
||||||
/* PCI_EXP_LINK{CAP, STA} */
|
/* PCI_EXP_LINK{CAP, STA} */
|
||||||
/* link speed */
|
/* link speed */
|
||||||
#define PCI_EXP_LNK_LS_25 1
|
#define PCI_EXP_LNK_LS_25 1
|
||||||
|
|
||||||
#define PCI_EXP_LNK_MLW_SHIFT (ffs(PCI_EXP_LNKCAP_MLW) - 1)
|
#define PCI_EXP_LNK_MLW_SHIFT ctz32(PCI_EXP_LNKCAP_MLW)
|
||||||
#define PCI_EXP_LNK_MLW_1 (1 << PCI_EXP_LNK_MLW_SHIFT)
|
#define PCI_EXP_LNK_MLW_1 (1 << PCI_EXP_LNK_MLW_SHIFT)
|
||||||
|
|
||||||
/* PCI_EXP_LINKCAP */
|
/* PCI_EXP_LINKCAP */
|
||||||
#define PCI_EXP_LNKCAP_ASPMS_SHIFT (ffs(PCI_EXP_LNKCAP_ASPMS) - 1)
|
#define PCI_EXP_LNKCAP_ASPMS_SHIFT ctz32(PCI_EXP_LNKCAP_ASPMS)
|
||||||
#define PCI_EXP_LNKCAP_ASPMS_0S (1 << PCI_EXP_LNKCAP_ASPMS_SHIFT)
|
#define PCI_EXP_LNKCAP_ASPMS_0S (1 << PCI_EXP_LNKCAP_ASPMS_SHIFT)
|
||||||
|
|
||||||
#define PCI_EXP_LNKCAP_PN_SHIFT (ffs(PCI_EXP_LNKCAP_PN) - 1)
|
#define PCI_EXP_LNKCAP_PN_SHIFT ctz32(PCI_EXP_LNKCAP_PN)
|
||||||
|
|
||||||
#define PCI_EXP_SLTCAP_PSN_SHIFT (ffs(PCI_EXP_SLTCAP_PSN) - 1)
|
#define PCI_EXP_SLTCAP_PSN_SHIFT ctz32(PCI_EXP_SLTCAP_PSN)
|
||||||
|
|
||||||
#define PCI_EXP_SLTCTL_IND_RESERVED 0x0
|
#define PCI_EXP_SLTCTL_IND_RESERVED 0x0
|
||||||
#define PCI_EXP_SLTCTL_IND_ON 0x1
|
#define PCI_EXP_SLTCTL_IND_ON 0x1
|
||||||
#define PCI_EXP_SLTCTL_IND_BLINK 0x2
|
#define PCI_EXP_SLTCTL_IND_BLINK 0x2
|
||||||
#define PCI_EXP_SLTCTL_IND_OFF 0x3
|
#define PCI_EXP_SLTCTL_IND_OFF 0x3
|
||||||
#define PCI_EXP_SLTCTL_AIC_SHIFT (ffs(PCI_EXP_SLTCTL_AIC) - 1)
|
#define PCI_EXP_SLTCTL_AIC_SHIFT ctz32(PCI_EXP_SLTCTL_AIC)
|
||||||
#define PCI_EXP_SLTCTL_AIC_OFF \
|
#define PCI_EXP_SLTCTL_AIC_OFF \
|
||||||
(PCI_EXP_SLTCTL_IND_OFF << PCI_EXP_SLTCTL_AIC_SHIFT)
|
(PCI_EXP_SLTCTL_IND_OFF << PCI_EXP_SLTCTL_AIC_SHIFT)
|
||||||
|
|
||||||
#define PCI_EXP_SLTCTL_PIC_SHIFT (ffs(PCI_EXP_SLTCTL_PIC) - 1)
|
#define PCI_EXP_SLTCTL_PIC_SHIFT ctz32(PCI_EXP_SLTCTL_PIC)
|
||||||
#define PCI_EXP_SLTCTL_PIC_OFF \
|
#define PCI_EXP_SLTCTL_PIC_OFF \
|
||||||
(PCI_EXP_SLTCTL_IND_OFF << PCI_EXP_SLTCTL_PIC_SHIFT)
|
(PCI_EXP_SLTCTL_IND_OFF << PCI_EXP_SLTCTL_PIC_SHIFT)
|
||||||
#define PCI_EXP_SLTCTL_PIC_ON \
|
#define PCI_EXP_SLTCTL_PIC_ON \
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
|
|
||||||
#define PCI_ERR_ROOT_IRQ_MAX 32
|
#define PCI_ERR_ROOT_IRQ_MAX 32
|
||||||
#define PCI_ERR_ROOT_IRQ 0xf8000000
|
#define PCI_ERR_ROOT_IRQ 0xf8000000
|
||||||
#define PCI_ERR_ROOT_IRQ_SHIFT (ffs(PCI_ERR_ROOT_IRQ) - 1)
|
#define PCI_ERR_ROOT_IRQ_SHIFT ctz32(PCI_ERR_ROOT_IRQ)
|
||||||
#define PCI_ERR_ROOT_STATUS_REPORT_MASK (PCI_ERR_ROOT_COR_RCV | \
|
#define PCI_ERR_ROOT_STATUS_REPORT_MASK (PCI_ERR_ROOT_COR_RCV | \
|
||||||
PCI_ERR_ROOT_MULTI_COR_RCV | \
|
PCI_ERR_ROOT_MULTI_COR_RCV | \
|
||||||
PCI_ERR_ROOT_UNCOR_RCV | \
|
PCI_ERR_ROOT_UNCOR_RCV | \
|
||||||
|
|
|
@ -37,9 +37,6 @@ void qerror_report_err(Error *err);
|
||||||
#define QERR_BASE_NOT_FOUND \
|
#define QERR_BASE_NOT_FOUND \
|
||||||
ERROR_CLASS_GENERIC_ERROR, "Base '%s' not found"
|
ERROR_CLASS_GENERIC_ERROR, "Base '%s' not found"
|
||||||
|
|
||||||
#define QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED \
|
|
||||||
ERROR_CLASS_GENERIC_ERROR, "Block format '%s' used by device '%s' does not support feature '%s'"
|
|
||||||
|
|
||||||
#define QERR_BLOCK_JOB_NOT_READY \
|
#define QERR_BLOCK_JOB_NOT_READY \
|
||||||
ERROR_CLASS_GENERIC_ERROR, "The active block job for device '%s' cannot be completed"
|
ERROR_CLASS_GENERIC_ERROR, "The active block job for device '%s' cannot be completed"
|
||||||
|
|
||||||
|
@ -58,9 +55,6 @@ void qerror_report_err(Error *err);
|
||||||
#define QERR_DEVICE_IN_USE \
|
#define QERR_DEVICE_IN_USE \
|
||||||
ERROR_CLASS_GENERIC_ERROR, "Device '%s' is in use"
|
ERROR_CLASS_GENERIC_ERROR, "Device '%s' is in use"
|
||||||
|
|
||||||
#define QERR_DEVICE_IS_READ_ONLY \
|
|
||||||
ERROR_CLASS_GENERIC_ERROR, "Device '%s' is read only"
|
|
||||||
|
|
||||||
#define QERR_DEVICE_NO_HOTPLUG \
|
#define QERR_DEVICE_NO_HOTPLUG \
|
||||||
ERROR_CLASS_GENERIC_ERROR, "Device '%s' does not support hotplugging"
|
ERROR_CLASS_GENERIC_ERROR, "Device '%s' does not support hotplugging"
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,29 @@ struct HBitmapIter {
|
||||||
*/
|
*/
|
||||||
HBitmap *hbitmap_alloc(uint64_t size, int granularity);
|
HBitmap *hbitmap_alloc(uint64_t size, int granularity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hbitmap_truncate:
|
||||||
|
* @hb: The bitmap to change the size of.
|
||||||
|
* @size: The number of elements to change the bitmap to accommodate.
|
||||||
|
*
|
||||||
|
* truncate or grow an existing bitmap to accommodate a new number of elements.
|
||||||
|
* This may invalidate existing HBitmapIterators.
|
||||||
|
*/
|
||||||
|
void hbitmap_truncate(HBitmap *hb, uint64_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hbitmap_merge:
|
||||||
|
* @a: The bitmap to store the result in.
|
||||||
|
* @b: The bitmap to merge into @a.
|
||||||
|
* @return true if the merge was successful,
|
||||||
|
* false if it was not attempted.
|
||||||
|
*
|
||||||
|
* Merge two bitmaps together.
|
||||||
|
* A := A (BITOR) B.
|
||||||
|
* B is left unmodified.
|
||||||
|
*/
|
||||||
|
bool hbitmap_merge(HBitmap *a, const HBitmap *b);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hbitmap_empty:
|
* hbitmap_empty:
|
||||||
* @hb: HBitmap to operate on.
|
* @hb: HBitmap to operate on.
|
||||||
|
|
|
@ -58,7 +58,7 @@ struct virtio_blk_config {
|
||||||
uint32_t size_max;
|
uint32_t size_max;
|
||||||
/* The maximum number of segments (if VIRTIO_BLK_F_SEG_MAX) */
|
/* The maximum number of segments (if VIRTIO_BLK_F_SEG_MAX) */
|
||||||
uint32_t seg_max;
|
uint32_t seg_max;
|
||||||
/* geometry the device (if VIRTIO_BLK_F_GEOMETRY) */
|
/* geometry of the device (if VIRTIO_BLK_F_GEOMETRY) */
|
||||||
struct virtio_blk_geometry {
|
struct virtio_blk_geometry {
|
||||||
uint16_t cylinders;
|
uint16_t cylinders;
|
||||||
uint8_t heads;
|
uint8_t heads;
|
||||||
|
@ -117,7 +117,11 @@ struct virtio_blk_config {
|
||||||
#define VIRTIO_BLK_T_BARRIER 0x80000000
|
#define VIRTIO_BLK_T_BARRIER 0x80000000
|
||||||
#endif /* !VIRTIO_BLK_NO_LEGACY */
|
#endif /* !VIRTIO_BLK_NO_LEGACY */
|
||||||
|
|
||||||
/* This is the first element of the read scatter-gather list. */
|
/*
|
||||||
|
* This comes first in the read scatter-gather list.
|
||||||
|
* For legacy virtio, if VIRTIO_F_ANY_LAYOUT is not negotiated,
|
||||||
|
* this is the first element of the read scatter-gather list.
|
||||||
|
*/
|
||||||
struct virtio_blk_outhdr {
|
struct virtio_blk_outhdr {
|
||||||
/* VIRTIO_BLK_T* */
|
/* VIRTIO_BLK_T* */
|
||||||
__virtio32 type;
|
__virtio32 type;
|
||||||
|
|
|
@ -87,6 +87,8 @@ int blk_read_unthrottled(BlockBackend *blk, int64_t sector_num, uint8_t *buf,
|
||||||
int nb_sectors);
|
int nb_sectors);
|
||||||
int blk_write(BlockBackend *blk, int64_t sector_num, const uint8_t *buf,
|
int blk_write(BlockBackend *blk, int64_t sector_num, const uint8_t *buf,
|
||||||
int nb_sectors);
|
int nb_sectors);
|
||||||
|
int blk_write_zeroes(BlockBackend *blk, int64_t sector_num,
|
||||||
|
int nb_sectors, BdrvRequestFlags flags);
|
||||||
BlockAIOCB *blk_aio_write_zeroes(BlockBackend *blk, int64_t sector_num,
|
BlockAIOCB *blk_aio_write_zeroes(BlockBackend *blk, int64_t sector_num,
|
||||||
int nb_sectors, BdrvRequestFlags flags,
|
int nb_sectors, BdrvRequestFlags flags,
|
||||||
BlockCompletionFunc *cb, void *opaque);
|
BlockCompletionFunc *cb, void *opaque);
|
||||||
|
|
|
@ -72,9 +72,6 @@
|
||||||
#define sigsetjmp(env, savemask) setjmp(env)
|
#define sigsetjmp(env, savemask) setjmp(env)
|
||||||
#define siglongjmp(env, val) longjmp(env, val)
|
#define siglongjmp(env, val) longjmp(env, val)
|
||||||
|
|
||||||
/* Declaration of ffs() is missing in MinGW's strings.h. */
|
|
||||||
int ffs(int i);
|
|
||||||
|
|
||||||
/* Missing POSIX functions. Don't use MinGW-w64 macros. */
|
/* Missing POSIX functions. Don't use MinGW-w64 macros. */
|
||||||
#undef gmtime_r
|
#undef gmtime_r
|
||||||
struct tm *gmtime_r(const time_t *timep, struct tm *result);
|
struct tm *gmtime_r(const time_t *timep, struct tm *result);
|
||||||
|
|
11
iothread.c
11
iothread.c
|
@ -31,21 +31,14 @@ typedef ObjectClass IOThreadClass;
|
||||||
static void *iothread_run(void *opaque)
|
static void *iothread_run(void *opaque)
|
||||||
{
|
{
|
||||||
IOThread *iothread = opaque;
|
IOThread *iothread = opaque;
|
||||||
bool blocking;
|
|
||||||
|
|
||||||
qemu_mutex_lock(&iothread->init_done_lock);
|
qemu_mutex_lock(&iothread->init_done_lock);
|
||||||
iothread->thread_id = qemu_get_thread_id();
|
iothread->thread_id = qemu_get_thread_id();
|
||||||
qemu_cond_signal(&iothread->init_done_cond);
|
qemu_cond_signal(&iothread->init_done_cond);
|
||||||
qemu_mutex_unlock(&iothread->init_done_lock);
|
qemu_mutex_unlock(&iothread->init_done_lock);
|
||||||
|
|
||||||
while (!iothread->stopping) {
|
while (!atomic_read(&iothread->stopping)) {
|
||||||
aio_context_acquire(iothread->ctx);
|
aio_poll(iothread->ctx, true);
|
||||||
blocking = true;
|
|
||||||
while (!iothread->stopping && aio_poll(iothread->ctx, blocking)) {
|
|
||||||
/* Progress was made, keep going */
|
|
||||||
blocking = false;
|
|
||||||
}
|
|
||||||
aio_context_release(iothread->ctx);
|
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1141,18 +1141,18 @@ static int kvm_irqchip_get_virq(KVMState *s)
|
||||||
{
|
{
|
||||||
uint32_t *word = s->used_gsi_bitmap;
|
uint32_t *word = s->used_gsi_bitmap;
|
||||||
int max_words = ALIGN(s->gsi_count, 32) / 32;
|
int max_words = ALIGN(s->gsi_count, 32) / 32;
|
||||||
int i, bit;
|
int i, zeroes;
|
||||||
bool retry = true;
|
bool retry = true;
|
||||||
|
|
||||||
again:
|
again:
|
||||||
/* Return the lowest unused GSI in the bitmap */
|
/* Return the lowest unused GSI in the bitmap */
|
||||||
for (i = 0; i < max_words; i++) {
|
for (i = 0; i < max_words; i++) {
|
||||||
bit = ffs(~word[i]);
|
zeroes = ctz32(~word[i]);
|
||||||
if (!bit) {
|
if (zeroes == 32) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return bit - 1 + i * 32;
|
return zeroes + i * 32;
|
||||||
}
|
}
|
||||||
if (!s->direct_msi && retry) {
|
if (!s->direct_msi && retry) {
|
||||||
retry = false;
|
retry = false;
|
||||||
|
|
|
@ -304,7 +304,7 @@ static int mig_save_device_bulk(QEMUFile *f, BlkMigDevState *bmds)
|
||||||
blk->aiocb = bdrv_aio_readv(bs, cur_sector, &blk->qiov,
|
blk->aiocb = bdrv_aio_readv(bs, cur_sector, &blk->qiov,
|
||||||
nr_sectors, blk_mig_read_cb, blk);
|
nr_sectors, blk_mig_read_cb, blk);
|
||||||
|
|
||||||
bdrv_reset_dirty_bitmap(bs, bmds->dirty_bitmap, cur_sector, nr_sectors);
|
bdrv_reset_dirty_bitmap(bmds->dirty_bitmap, cur_sector, nr_sectors);
|
||||||
qemu_mutex_unlock_iothread();
|
qemu_mutex_unlock_iothread();
|
||||||
|
|
||||||
bmds->cur_sector = cur_sector + nr_sectors;
|
bmds->cur_sector = cur_sector + nr_sectors;
|
||||||
|
@ -320,7 +320,7 @@ static int set_dirty_tracking(void)
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
||||||
bmds->dirty_bitmap = bdrv_create_dirty_bitmap(bmds->bs, BLOCK_SIZE,
|
bmds->dirty_bitmap = bdrv_create_dirty_bitmap(bmds->bs, BLOCK_SIZE,
|
||||||
NULL);
|
NULL, NULL);
|
||||||
if (!bmds->dirty_bitmap) {
|
if (!bmds->dirty_bitmap) {
|
||||||
ret = -errno;
|
ret = -errno;
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -497,8 +497,7 @@ static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds,
|
||||||
g_free(blk);
|
g_free(blk);
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_reset_dirty_bitmap(bmds->bs, bmds->dirty_bitmap, sector,
|
bdrv_reset_dirty_bitmap(bmds->dirty_bitmap, sector, nr_sectors);
|
||||||
nr_sectors);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sector += BDRV_SECTORS_PER_DIRTY_CHUNK;
|
sector += BDRV_SECTORS_PER_DIRTY_CHUNK;
|
||||||
|
@ -584,7 +583,7 @@ static int64_t get_remaining_dirty(void)
|
||||||
int64_t dirty = 0;
|
int64_t dirty = 0;
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
|
||||||
dirty += bdrv_get_dirty_count(bmds->bs, bmds->dirty_bitmap);
|
dirty += bdrv_get_dirty_count(bmds->dirty_bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dirty << BDRV_SECTOR_BITS;
|
return dirty << BDRV_SECTOR_BITS;
|
||||||
|
|
|
@ -330,14 +330,19 @@
|
||||||
#
|
#
|
||||||
# Block dirty bitmap information.
|
# Block dirty bitmap information.
|
||||||
#
|
#
|
||||||
|
# @name: #optional the name of the dirty bitmap (Since 2.4)
|
||||||
|
#
|
||||||
# @count: number of dirty bytes according to the dirty bitmap
|
# @count: number of dirty bytes according to the dirty bitmap
|
||||||
#
|
#
|
||||||
# @granularity: granularity of the dirty bitmap in bytes (since 1.4)
|
# @granularity: granularity of the dirty bitmap in bytes (since 1.4)
|
||||||
#
|
#
|
||||||
|
# @frozen: whether the dirty bitmap is frozen (Since 2.4)
|
||||||
|
#
|
||||||
# Since: 1.3
|
# Since: 1.3
|
||||||
##
|
##
|
||||||
{ 'type': 'BlockDirtyInfo',
|
{ 'type': 'BlockDirtyInfo',
|
||||||
'data': {'count': 'int', 'granularity': 'int'} }
|
'data': {'*name': 'str', 'count': 'int', 'granularity': 'uint32',
|
||||||
|
'frozen': 'bool'} }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockInfo:
|
# @BlockInfo:
|
||||||
|
@ -510,10 +515,12 @@
|
||||||
#
|
#
|
||||||
# @none: only copy data written from now on
|
# @none: only copy data written from now on
|
||||||
#
|
#
|
||||||
|
# @dirty-bitmap: only copy data described by the dirty bitmap. Since: 2.4
|
||||||
|
#
|
||||||
# Since: 1.3
|
# Since: 1.3
|
||||||
##
|
##
|
||||||
{ 'enum': 'MirrorSyncMode',
|
{ 'enum': 'MirrorSyncMode',
|
||||||
'data': ['top', 'full', 'none'] }
|
'data': ['top', 'full', 'none', 'dirty-bitmap'] }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockJobType:
|
# @BlockJobType:
|
||||||
|
@ -688,14 +695,18 @@
|
||||||
# probe if @mode is 'existing', else the format of the source
|
# probe if @mode is 'existing', else the format of the source
|
||||||
#
|
#
|
||||||
# @sync: what parts of the disk image should be copied to the destination
|
# @sync: what parts of the disk image should be copied to the destination
|
||||||
# (all the disk, only the sectors allocated in the topmost image, or
|
# (all the disk, only the sectors allocated in the topmost image, from a
|
||||||
# only new I/O).
|
# dirty bitmap, or only new I/O).
|
||||||
#
|
#
|
||||||
# @mode: #optional whether and how QEMU should create a new image, default is
|
# @mode: #optional whether and how QEMU should create a new image, default is
|
||||||
# 'absolute-paths'.
|
# 'absolute-paths'.
|
||||||
#
|
#
|
||||||
# @speed: #optional the maximum speed, in bytes per second
|
# @speed: #optional the maximum speed, in bytes per second
|
||||||
#
|
#
|
||||||
|
# @bitmap: #optional the name of dirty bitmap if sync is "dirty-bitmap".
|
||||||
|
# Must be present if sync is "dirty-bitmap", must NOT be present
|
||||||
|
# otherwise. (Since 2.4)
|
||||||
|
#
|
||||||
# @on-source-error: #optional the action to take on an error on the source,
|
# @on-source-error: #optional the action to take on an error on the source,
|
||||||
# default 'report'. 'stop' and 'enospc' can only be used
|
# default 'report'. 'stop' and 'enospc' can only be used
|
||||||
# if the block device supports io-status (see BlockInfo).
|
# if the block device supports io-status (see BlockInfo).
|
||||||
|
@ -713,7 +724,7 @@
|
||||||
{ 'type': 'DriveBackup',
|
{ 'type': 'DriveBackup',
|
||||||
'data': { 'device': 'str', 'target': 'str', '*format': 'str',
|
'data': { 'device': 'str', 'target': 'str', '*format': 'str',
|
||||||
'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
|
'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
|
||||||
'*speed': 'int',
|
'*speed': 'int', '*bitmap': 'str',
|
||||||
'*on-source-error': 'BlockdevOnError',
|
'*on-source-error': 'BlockdevOnError',
|
||||||
'*on-target-error': 'BlockdevOnError' } }
|
'*on-target-error': 'BlockdevOnError' } }
|
||||||
|
|
||||||
|
@ -957,6 +968,76 @@
|
||||||
'*buf-size': 'int', '*on-source-error': 'BlockdevOnError',
|
'*buf-size': 'int', '*on-source-error': 'BlockdevOnError',
|
||||||
'*on-target-error': 'BlockdevOnError' } }
|
'*on-target-error': 'BlockdevOnError' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @BlockDirtyBitmap
|
||||||
|
#
|
||||||
|
# @node: name of device/node which the bitmap is tracking
|
||||||
|
#
|
||||||
|
# @name: name of the dirty bitmap
|
||||||
|
#
|
||||||
|
# Since 2.4
|
||||||
|
##
|
||||||
|
{ 'type': 'BlockDirtyBitmap',
|
||||||
|
'data': { 'node': 'str', 'name': 'str' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @BlockDirtyBitmapAdd
|
||||||
|
#
|
||||||
|
# @node: name of device/node which the bitmap is tracking
|
||||||
|
#
|
||||||
|
# @name: name of the dirty bitmap
|
||||||
|
#
|
||||||
|
# @granularity: #optional the bitmap granularity, default is 64k for
|
||||||
|
# block-dirty-bitmap-add
|
||||||
|
#
|
||||||
|
# Since 2.4
|
||||||
|
##
|
||||||
|
{ 'type': 'BlockDirtyBitmapAdd',
|
||||||
|
'data': { 'node': 'str', 'name': 'str', '*granularity': 'uint32' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @block-dirty-bitmap-add
|
||||||
|
#
|
||||||
|
# Create a dirty bitmap with a name on the node
|
||||||
|
#
|
||||||
|
# Returns: nothing on success
|
||||||
|
# If @node is not a valid block device or node, DeviceNotFound
|
||||||
|
# If @name is already taken, GenericError with an explanation
|
||||||
|
#
|
||||||
|
# Since 2.4
|
||||||
|
##
|
||||||
|
{ 'command': 'block-dirty-bitmap-add',
|
||||||
|
'data': 'BlockDirtyBitmapAdd' }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @block-dirty-bitmap-remove
|
||||||
|
#
|
||||||
|
# Remove a dirty bitmap on the node
|
||||||
|
#
|
||||||
|
# Returns: nothing on success
|
||||||
|
# If @node is not a valid block device or node, DeviceNotFound
|
||||||
|
# If @name is not found, GenericError with an explanation
|
||||||
|
# if @name is frozen by an operation, GenericError
|
||||||
|
#
|
||||||
|
# Since 2.4
|
||||||
|
##
|
||||||
|
{ 'command': 'block-dirty-bitmap-remove',
|
||||||
|
'data': 'BlockDirtyBitmap' }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @block-dirty-bitmap-clear
|
||||||
|
#
|
||||||
|
# Clear (reset) a dirty bitmap on the device
|
||||||
|
#
|
||||||
|
# Returns: nothing on success
|
||||||
|
# If @node is not a valid block device, DeviceNotFound
|
||||||
|
# If @name is not found, GenericError with an explanation
|
||||||
|
#
|
||||||
|
# Since 2.4
|
||||||
|
##
|
||||||
|
{ 'command': 'block-dirty-bitmap-clear',
|
||||||
|
'data': 'BlockDirtyBitmap' }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @block_set_io_throttle:
|
# @block_set_io_throttle:
|
||||||
#
|
#
|
||||||
|
@ -1310,11 +1391,14 @@
|
||||||
# Driver specific block device options for the null backend.
|
# Driver specific block device options for the null backend.
|
||||||
#
|
#
|
||||||
# @size: #optional size of the device in bytes.
|
# @size: #optional size of the device in bytes.
|
||||||
|
# @latency-ns: #optional emulated latency (in nanoseconds) in processing
|
||||||
|
# requests. Default to zero which completes requests immediately.
|
||||||
|
# (Since 2.4)
|
||||||
#
|
#
|
||||||
# Since: 2.2
|
# Since: 2.2
|
||||||
##
|
##
|
||||||
{ 'type': 'BlockdevOptionsNull',
|
{ 'type': 'BlockdevOptionsNull',
|
||||||
'data': { '*size': 'int' } }
|
'data': { '*size': 'int', '*latency-ns': 'uint64' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockdevOptionsVVFAT
|
# @BlockdevOptionsVVFAT
|
||||||
|
@ -1754,7 +1838,11 @@
|
||||||
#
|
#
|
||||||
# Emitted when a corruption has been detected in a disk image
|
# Emitted when a corruption has been detected in a disk image
|
||||||
#
|
#
|
||||||
# @device: device name
|
# @device: device name. This is always present for compatibility
|
||||||
|
# reasons, but it can be empty ("") if the image does not
|
||||||
|
# have a device name associated.
|
||||||
|
#
|
||||||
|
# @node-name: #optional node name (Since: 2.4)
|
||||||
#
|
#
|
||||||
# @msg: informative message for human consumption, such as the kind of
|
# @msg: informative message for human consumption, such as the kind of
|
||||||
# corruption being detected. It should not be parsed by machine as it is
|
# corruption being detected. It should not be parsed by machine as it is
|
||||||
|
@ -1773,11 +1861,12 @@
|
||||||
# Since: 1.7
|
# Since: 1.7
|
||||||
##
|
##
|
||||||
{ 'event': 'BLOCK_IMAGE_CORRUPTED',
|
{ 'event': 'BLOCK_IMAGE_CORRUPTED',
|
||||||
'data': { 'device' : 'str',
|
'data': { 'device' : 'str',
|
||||||
'msg' : 'str',
|
'*node-name' : 'str',
|
||||||
'*offset': 'int',
|
'msg' : 'str',
|
||||||
'*size' : 'int',
|
'*offset' : 'int',
|
||||||
'fatal' : 'bool' } }
|
'*size' : 'int',
|
||||||
|
'fatal' : 'bool' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BLOCK_IO_ERROR
|
# @BLOCK_IO_ERROR
|
||||||
|
|
516
qemu-img.c
516
qemu-img.c
|
@ -1305,20 +1305,312 @@ out3:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ImgConvertBlockStatus {
|
||||||
|
BLK_DATA,
|
||||||
|
BLK_ZERO,
|
||||||
|
BLK_BACKING_FILE,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct ImgConvertState {
|
||||||
|
BlockBackend **src;
|
||||||
|
int64_t *src_sectors;
|
||||||
|
int src_cur, src_num;
|
||||||
|
int64_t src_cur_offset;
|
||||||
|
int64_t total_sectors;
|
||||||
|
int64_t allocated_sectors;
|
||||||
|
enum ImgConvertBlockStatus status;
|
||||||
|
int64_t sector_next_status;
|
||||||
|
BlockBackend *target;
|
||||||
|
bool has_zero_init;
|
||||||
|
bool compressed;
|
||||||
|
bool target_has_backing;
|
||||||
|
int min_sparse;
|
||||||
|
size_t cluster_sectors;
|
||||||
|
size_t buf_sectors;
|
||||||
|
} ImgConvertState;
|
||||||
|
|
||||||
|
static void convert_select_part(ImgConvertState *s, int64_t sector_num)
|
||||||
|
{
|
||||||
|
assert(sector_num >= s->src_cur_offset);
|
||||||
|
while (sector_num - s->src_cur_offset >= s->src_sectors[s->src_cur]) {
|
||||||
|
s->src_cur_offset += s->src_sectors[s->src_cur];
|
||||||
|
s->src_cur++;
|
||||||
|
assert(s->src_cur < s->src_num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num)
|
||||||
|
{
|
||||||
|
int64_t ret;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
convert_select_part(s, sector_num);
|
||||||
|
|
||||||
|
assert(s->total_sectors > sector_num);
|
||||||
|
n = MIN(s->total_sectors - sector_num, BDRV_REQUEST_MAX_SECTORS);
|
||||||
|
|
||||||
|
if (s->sector_next_status <= sector_num) {
|
||||||
|
ret = bdrv_get_block_status(blk_bs(s->src[s->src_cur]),
|
||||||
|
sector_num - s->src_cur_offset,
|
||||||
|
n, &n);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret & BDRV_BLOCK_ZERO) {
|
||||||
|
s->status = BLK_ZERO;
|
||||||
|
} else if (ret & BDRV_BLOCK_DATA) {
|
||||||
|
s->status = BLK_DATA;
|
||||||
|
} else if (!s->target_has_backing) {
|
||||||
|
/* Without a target backing file we must copy over the contents of
|
||||||
|
* the backing file as well. */
|
||||||
|
/* TODO Check block status of the backing file chain to avoid
|
||||||
|
* needlessly reading zeroes and limiting the iteration to the
|
||||||
|
* buffer size */
|
||||||
|
s->status = BLK_DATA;
|
||||||
|
} else {
|
||||||
|
s->status = BLK_BACKING_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->sector_next_status = sector_num + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
n = MIN(n, s->sector_next_status - sector_num);
|
||||||
|
if (s->status == BLK_DATA) {
|
||||||
|
n = MIN(n, s->buf_sectors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We need to write complete clusters for compressed images, so if an
|
||||||
|
* unallocated area is shorter than that, we must consider the whole
|
||||||
|
* cluster allocated. */
|
||||||
|
if (s->compressed) {
|
||||||
|
if (n < s->cluster_sectors) {
|
||||||
|
n = MIN(s->cluster_sectors, s->total_sectors - sector_num);
|
||||||
|
s->status = BLK_DATA;
|
||||||
|
} else {
|
||||||
|
n = QEMU_ALIGN_DOWN(n, s->cluster_sectors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int convert_read(ImgConvertState *s, int64_t sector_num, int nb_sectors,
|
||||||
|
uint8_t *buf)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (s->status == BLK_ZERO || s->status == BLK_BACKING_FILE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(nb_sectors <= s->buf_sectors);
|
||||||
|
while (nb_sectors > 0) {
|
||||||
|
BlockBackend *blk;
|
||||||
|
int64_t bs_sectors;
|
||||||
|
|
||||||
|
/* In the case of compression with multiple source files, we can get a
|
||||||
|
* nb_sectors that spreads into the next part. So we must be able to
|
||||||
|
* read across multiple BDSes for one convert_read() call. */
|
||||||
|
convert_select_part(s, sector_num);
|
||||||
|
blk = s->src[s->src_cur];
|
||||||
|
bs_sectors = s->src_sectors[s->src_cur];
|
||||||
|
|
||||||
|
n = MIN(nb_sectors, bs_sectors - (sector_num - s->src_cur_offset));
|
||||||
|
ret = blk_read(blk, sector_num - s->src_cur_offset, buf, n);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sector_num += n;
|
||||||
|
nb_sectors -= n;
|
||||||
|
buf += n * BDRV_SECTOR_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors,
|
||||||
|
const uint8_t *buf)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
while (nb_sectors > 0) {
|
||||||
|
int n = nb_sectors;
|
||||||
|
|
||||||
|
switch (s->status) {
|
||||||
|
case BLK_BACKING_FILE:
|
||||||
|
/* If we have a backing file, leave clusters unallocated that are
|
||||||
|
* unallocated in the source image, so that the backing file is
|
||||||
|
* visible at the respective offset. */
|
||||||
|
assert(s->target_has_backing);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLK_DATA:
|
||||||
|
/* We must always write compressed clusters as a whole, so don't
|
||||||
|
* try to find zeroed parts in the buffer. We can only save the
|
||||||
|
* write if the buffer is completely zeroed and we're allowed to
|
||||||
|
* keep the target sparse. */
|
||||||
|
if (s->compressed) {
|
||||||
|
if (s->has_zero_init && s->min_sparse &&
|
||||||
|
buffer_is_zero(buf, n * BDRV_SECTOR_SIZE))
|
||||||
|
{
|
||||||
|
assert(!s->target_has_backing);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = blk_write_compressed(s->target, sector_num, buf, n);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there is real non-zero data or we're told to keep the target
|
||||||
|
* fully allocated (-S 0), we must write it. Otherwise we can treat
|
||||||
|
* it as zero sectors. */
|
||||||
|
if (!s->min_sparse ||
|
||||||
|
is_allocated_sectors_min(buf, n, &n, s->min_sparse))
|
||||||
|
{
|
||||||
|
ret = blk_write(s->target, sector_num, buf, n);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* fall-through */
|
||||||
|
|
||||||
|
case BLK_ZERO:
|
||||||
|
if (s->has_zero_init) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ret = blk_write_zeroes(s->target, sector_num, n, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sector_num += n;
|
||||||
|
nb_sectors -= n;
|
||||||
|
buf += n * BDRV_SECTOR_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int convert_do_copy(ImgConvertState *s)
|
||||||
|
{
|
||||||
|
uint8_t *buf = NULL;
|
||||||
|
int64_t sector_num, allocated_done;
|
||||||
|
int ret;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
/* Check whether we have zero initialisation or can get it efficiently */
|
||||||
|
s->has_zero_init = s->min_sparse && !s->target_has_backing
|
||||||
|
? bdrv_has_zero_init(blk_bs(s->target))
|
||||||
|
: false;
|
||||||
|
|
||||||
|
if (!s->has_zero_init && !s->target_has_backing &&
|
||||||
|
bdrv_can_write_zeroes_with_unmap(blk_bs(s->target)))
|
||||||
|
{
|
||||||
|
ret = bdrv_make_zero(blk_bs(s->target), BDRV_REQ_MAY_UNMAP);
|
||||||
|
if (ret == 0) {
|
||||||
|
s->has_zero_init = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate buffer for copied data. For compressed images, only one cluster
|
||||||
|
* can be copied at a time. */
|
||||||
|
if (s->compressed) {
|
||||||
|
if (s->cluster_sectors <= 0 || s->cluster_sectors > s->buf_sectors) {
|
||||||
|
error_report("invalid cluster size");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
s->buf_sectors = s->cluster_sectors;
|
||||||
|
}
|
||||||
|
buf = blk_blockalign(s->target, s->buf_sectors * BDRV_SECTOR_SIZE);
|
||||||
|
|
||||||
|
/* Calculate allocated sectors for progress */
|
||||||
|
s->allocated_sectors = 0;
|
||||||
|
sector_num = 0;
|
||||||
|
while (sector_num < s->total_sectors) {
|
||||||
|
n = convert_iteration_sectors(s, sector_num);
|
||||||
|
if (n < 0) {
|
||||||
|
ret = n;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (s->status == BLK_DATA) {
|
||||||
|
s->allocated_sectors += n;
|
||||||
|
}
|
||||||
|
sector_num += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do the copy */
|
||||||
|
s->src_cur = 0;
|
||||||
|
s->src_cur_offset = 0;
|
||||||
|
s->sector_next_status = 0;
|
||||||
|
|
||||||
|
sector_num = 0;
|
||||||
|
allocated_done = 0;
|
||||||
|
|
||||||
|
while (sector_num < s->total_sectors) {
|
||||||
|
n = convert_iteration_sectors(s, sector_num);
|
||||||
|
if (n < 0) {
|
||||||
|
ret = n;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (s->status == BLK_DATA) {
|
||||||
|
allocated_done += n;
|
||||||
|
qemu_progress_print(100.0 * allocated_done / s->allocated_sectors,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = convert_read(s, sector_num, n, buf);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_report("error while reading sector %" PRId64
|
||||||
|
": %s", sector_num, strerror(-ret));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = convert_write(s, sector_num, n, buf);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_report("error while writing sector %" PRId64
|
||||||
|
": %s", sector_num, strerror(-ret));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
sector_num += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s->compressed) {
|
||||||
|
/* signal EOF to align */
|
||||||
|
ret = blk_write_compressed(s->target, 0, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
fail:
|
||||||
|
qemu_vfree(buf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int img_convert(int argc, char **argv)
|
static int img_convert(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int c, n, n1, bs_n, bs_i, compress, cluster_sectors, skip_create;
|
int c, bs_n, bs_i, compress, cluster_sectors, skip_create;
|
||||||
int64_t ret = 0;
|
int64_t ret = 0;
|
||||||
int progress = 0, flags, src_flags;
|
int progress = 0, flags, src_flags;
|
||||||
const char *fmt, *out_fmt, *cache, *src_cache, *out_baseimg, *out_filename;
|
const char *fmt, *out_fmt, *cache, *src_cache, *out_baseimg, *out_filename;
|
||||||
BlockDriver *drv, *proto_drv;
|
BlockDriver *drv, *proto_drv;
|
||||||
BlockBackend **blk = NULL, *out_blk = NULL;
|
BlockBackend **blk = NULL, *out_blk = NULL;
|
||||||
BlockDriverState **bs = NULL, *out_bs = NULL;
|
BlockDriverState **bs = NULL, *out_bs = NULL;
|
||||||
int64_t total_sectors, nb_sectors, sector_num, bs_offset;
|
int64_t total_sectors;
|
||||||
int64_t *bs_sectors = NULL;
|
int64_t *bs_sectors = NULL;
|
||||||
uint8_t * buf = NULL;
|
|
||||||
size_t bufsectors = IO_BUF_SIZE / BDRV_SECTOR_SIZE;
|
size_t bufsectors = IO_BUF_SIZE / BDRV_SECTOR_SIZE;
|
||||||
const uint8_t *buf1;
|
|
||||||
BlockDriverInfo bdi;
|
BlockDriverInfo bdi;
|
||||||
QemuOpts *opts = NULL;
|
QemuOpts *opts = NULL;
|
||||||
QemuOptsList *create_opts = NULL;
|
QemuOptsList *create_opts = NULL;
|
||||||
|
@ -1329,6 +1621,7 @@ static int img_convert(int argc, char **argv)
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
QemuOpts *sn_opts = NULL;
|
QemuOpts *sn_opts = NULL;
|
||||||
|
ImgConvertState state;
|
||||||
|
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
out_fmt = "raw";
|
out_fmt = "raw";
|
||||||
|
@ -1627,9 +1920,6 @@ static int img_convert(int argc, char **argv)
|
||||||
}
|
}
|
||||||
out_bs = blk_bs(out_blk);
|
out_bs = blk_bs(out_blk);
|
||||||
|
|
||||||
bs_i = 0;
|
|
||||||
bs_offset = 0;
|
|
||||||
|
|
||||||
/* increase bufsectors from the default 4096 (2M) if opt_transfer_length
|
/* increase bufsectors from the default 4096 (2M) if opt_transfer_length
|
||||||
* or discard_alignment of the out_bs is greater. Limit to 32768 (16MB)
|
* or discard_alignment of the out_bs is greater. Limit to 32768 (16MB)
|
||||||
* as maximum. */
|
* as maximum. */
|
||||||
|
@ -1638,8 +1928,6 @@ static int img_convert(int argc, char **argv)
|
||||||
out_bs->bl.discard_alignment))
|
out_bs->bl.discard_alignment))
|
||||||
);
|
);
|
||||||
|
|
||||||
buf = blk_blockalign(out_blk, bufsectors * BDRV_SECTOR_SIZE);
|
|
||||||
|
|
||||||
if (skip_create) {
|
if (skip_create) {
|
||||||
int64_t output_sectors = blk_nb_sectors(out_blk);
|
int64_t output_sectors = blk_nb_sectors(out_blk);
|
||||||
if (output_sectors < 0) {
|
if (output_sectors < 0) {
|
||||||
|
@ -1666,203 +1954,20 @@ static int img_convert(int argc, char **argv)
|
||||||
cluster_sectors = bdi.cluster_size / BDRV_SECTOR_SIZE;
|
cluster_sectors = bdi.cluster_size / BDRV_SECTOR_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compress) {
|
state = (ImgConvertState) {
|
||||||
if (cluster_sectors <= 0 || cluster_sectors > bufsectors) {
|
.src = blk,
|
||||||
error_report("invalid cluster size");
|
.src_sectors = bs_sectors,
|
||||||
ret = -1;
|
.src_num = bs_n,
|
||||||
goto out;
|
.total_sectors = total_sectors,
|
||||||
}
|
.target = out_blk,
|
||||||
sector_num = 0;
|
.compressed = compress,
|
||||||
|
.target_has_backing = (bool) out_baseimg,
|
||||||
|
.min_sparse = min_sparse,
|
||||||
|
.cluster_sectors = cluster_sectors,
|
||||||
|
.buf_sectors = bufsectors,
|
||||||
|
};
|
||||||
|
ret = convert_do_copy(&state);
|
||||||
|
|
||||||
nb_sectors = total_sectors;
|
|
||||||
|
|
||||||
for(;;) {
|
|
||||||
int64_t bs_num;
|
|
||||||
int remainder;
|
|
||||||
uint8_t *buf2;
|
|
||||||
|
|
||||||
nb_sectors = total_sectors - sector_num;
|
|
||||||
if (nb_sectors <= 0)
|
|
||||||
break;
|
|
||||||
if (nb_sectors >= cluster_sectors)
|
|
||||||
n = cluster_sectors;
|
|
||||||
else
|
|
||||||
n = nb_sectors;
|
|
||||||
|
|
||||||
bs_num = sector_num - bs_offset;
|
|
||||||
assert (bs_num >= 0);
|
|
||||||
remainder = n;
|
|
||||||
buf2 = buf;
|
|
||||||
while (remainder > 0) {
|
|
||||||
int nlow;
|
|
||||||
while (bs_num == bs_sectors[bs_i]) {
|
|
||||||
bs_offset += bs_sectors[bs_i];
|
|
||||||
bs_i++;
|
|
||||||
assert (bs_i < bs_n);
|
|
||||||
bs_num = 0;
|
|
||||||
/* printf("changing part: sector_num=%" PRId64 ", "
|
|
||||||
"bs_i=%d, bs_offset=%" PRId64 ", bs_sectors=%" PRId64
|
|
||||||
"\n", sector_num, bs_i, bs_offset, bs_sectors[bs_i]); */
|
|
||||||
}
|
|
||||||
assert (bs_num < bs_sectors[bs_i]);
|
|
||||||
|
|
||||||
nlow = remainder > bs_sectors[bs_i] - bs_num
|
|
||||||
? bs_sectors[bs_i] - bs_num : remainder;
|
|
||||||
|
|
||||||
ret = blk_read(blk[bs_i], bs_num, buf2, nlow);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_report("error while reading sector %" PRId64 ": %s",
|
|
||||||
bs_num, strerror(-ret));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf2 += nlow * 512;
|
|
||||||
bs_num += nlow;
|
|
||||||
|
|
||||||
remainder -= nlow;
|
|
||||||
}
|
|
||||||
assert (remainder == 0);
|
|
||||||
|
|
||||||
if (!buffer_is_zero(buf, n * BDRV_SECTOR_SIZE)) {
|
|
||||||
ret = blk_write_compressed(out_blk, sector_num, buf, n);
|
|
||||||
if (ret != 0) {
|
|
||||||
error_report("error while compressing sector %" PRId64
|
|
||||||
": %s", sector_num, strerror(-ret));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sector_num += n;
|
|
||||||
qemu_progress_print(100.0 * sector_num / total_sectors, 0);
|
|
||||||
}
|
|
||||||
/* signal EOF to align */
|
|
||||||
blk_write_compressed(out_blk, 0, NULL, 0);
|
|
||||||
} else {
|
|
||||||
int64_t sectors_to_read, sectors_read, sector_num_next_status;
|
|
||||||
bool count_allocated_sectors;
|
|
||||||
int has_zero_init = min_sparse ? bdrv_has_zero_init(out_bs) : 0;
|
|
||||||
|
|
||||||
if (!has_zero_init && bdrv_can_write_zeroes_with_unmap(out_bs)) {
|
|
||||||
ret = bdrv_make_zero(out_bs, BDRV_REQ_MAY_UNMAP);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
has_zero_init = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sectors_to_read = total_sectors;
|
|
||||||
count_allocated_sectors = progress && (out_baseimg || has_zero_init);
|
|
||||||
restart:
|
|
||||||
sector_num = 0; // total number of sectors converted so far
|
|
||||||
sectors_read = 0;
|
|
||||||
sector_num_next_status = 0;
|
|
||||||
|
|
||||||
for(;;) {
|
|
||||||
nb_sectors = total_sectors - sector_num;
|
|
||||||
if (nb_sectors <= 0) {
|
|
||||||
if (count_allocated_sectors) {
|
|
||||||
sectors_to_read = sectors_read;
|
|
||||||
count_allocated_sectors = false;
|
|
||||||
goto restart;
|
|
||||||
}
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (sector_num - bs_offset >= bs_sectors[bs_i]) {
|
|
||||||
bs_offset += bs_sectors[bs_i];
|
|
||||||
bs_i ++;
|
|
||||||
assert (bs_i < bs_n);
|
|
||||||
/* printf("changing part: sector_num=%" PRId64 ", bs_i=%d, "
|
|
||||||
"bs_offset=%" PRId64 ", bs_sectors=%" PRId64 "\n",
|
|
||||||
sector_num, bs_i, bs_offset, bs_sectors[bs_i]); */
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((out_baseimg || has_zero_init) &&
|
|
||||||
sector_num >= sector_num_next_status) {
|
|
||||||
n = nb_sectors > INT_MAX ? INT_MAX : nb_sectors;
|
|
||||||
ret = bdrv_get_block_status(bs[bs_i], sector_num - bs_offset,
|
|
||||||
n, &n1);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_report("error while reading block status of sector %"
|
|
||||||
PRId64 ": %s", sector_num - bs_offset,
|
|
||||||
strerror(-ret));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
/* If the output image is zero initialized, we are not working
|
|
||||||
* on a shared base and the input is zero we can skip the next
|
|
||||||
* n1 sectors */
|
|
||||||
if (has_zero_init && !out_baseimg && (ret & BDRV_BLOCK_ZERO)) {
|
|
||||||
sector_num += n1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/* If the output image is being created as a copy on write
|
|
||||||
* image, assume that sectors which are unallocated in the
|
|
||||||
* input image are present in both the output's and input's
|
|
||||||
* base images (no need to copy them). */
|
|
||||||
if (out_baseimg) {
|
|
||||||
if (!(ret & BDRV_BLOCK_DATA)) {
|
|
||||||
sector_num += n1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/* The next 'n1' sectors are allocated in the input image.
|
|
||||||
* Copy only those as they may be followed by unallocated
|
|
||||||
* sectors. */
|
|
||||||
nb_sectors = n1;
|
|
||||||
}
|
|
||||||
/* avoid redundant callouts to get_block_status */
|
|
||||||
sector_num_next_status = sector_num + n1;
|
|
||||||
}
|
|
||||||
|
|
||||||
n = MIN(nb_sectors, bufsectors);
|
|
||||||
|
|
||||||
/* round down request length to an aligned sector, but
|
|
||||||
* do not bother doing this on short requests. They happen
|
|
||||||
* when we found an all-zero area, and the next sector to
|
|
||||||
* write will not be sector_num + n. */
|
|
||||||
if (cluster_sectors > 0 && n >= cluster_sectors) {
|
|
||||||
int64_t next_aligned_sector = (sector_num + n);
|
|
||||||
next_aligned_sector -= next_aligned_sector % cluster_sectors;
|
|
||||||
if (sector_num + n > next_aligned_sector) {
|
|
||||||
n = next_aligned_sector - sector_num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n = MIN(n, bs_sectors[bs_i] - (sector_num - bs_offset));
|
|
||||||
|
|
||||||
sectors_read += n;
|
|
||||||
if (count_allocated_sectors) {
|
|
||||||
sector_num += n;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
n1 = n;
|
|
||||||
ret = blk_read(blk[bs_i], sector_num - bs_offset, buf, n);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_report("error while reading sector %" PRId64 ": %s",
|
|
||||||
sector_num - bs_offset, strerror(-ret));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
/* NOTE: at the same time we convert, we do not write zero
|
|
||||||
sectors to have a chance to compress the image. Ideally, we
|
|
||||||
should add a specific call to have the info to go faster */
|
|
||||||
buf1 = buf;
|
|
||||||
while (n > 0) {
|
|
||||||
if (!has_zero_init ||
|
|
||||||
is_allocated_sectors_min(buf1, n, &n1, min_sparse)) {
|
|
||||||
ret = blk_write(out_blk, sector_num, buf1, n1);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_report("error while writing sector %" PRId64
|
|
||||||
": %s", sector_num, strerror(-ret));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sector_num += n1;
|
|
||||||
n -= n1;
|
|
||||||
buf1 += n1 * 512;
|
|
||||||
}
|
|
||||||
qemu_progress_print(100.0 * sectors_read / sectors_to_read, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out:
|
out:
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
qemu_progress_print(100, 0);
|
qemu_progress_print(100, 0);
|
||||||
|
@ -1870,7 +1975,6 @@ out:
|
||||||
qemu_progress_end();
|
qemu_progress_end();
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
qemu_opts_free(create_opts);
|
qemu_opts_free(create_opts);
|
||||||
qemu_vfree(buf);
|
|
||||||
qemu_opts_del(sn_opts);
|
qemu_opts_del(sn_opts);
|
||||||
blk_unref(out_blk);
|
blk_unref(out_blk);
|
||||||
g_free(bs);
|
g_free(bs);
|
||||||
|
|
130
qmp-commands.hx
130
qmp-commands.hx
|
@ -1007,6 +1007,43 @@ EQMP
|
||||||
.mhandler.cmd_new = qmp_marshal_input_block_stream,
|
.mhandler.cmd_new = qmp_marshal_input_block_stream,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
SQMP
|
||||||
|
block-stream
|
||||||
|
------------
|
||||||
|
|
||||||
|
Copy data from a backing file into a block device.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
- "device": The device's ID, must be unique (json-string)
|
||||||
|
- "base": The file name of the backing image above which copying starts
|
||||||
|
(json-string, optional)
|
||||||
|
- "backing-file": The backing file string to write into the active layer. This
|
||||||
|
filename is not validated.
|
||||||
|
|
||||||
|
If a pathname string is such that it cannot be resolved by
|
||||||
|
QEMU, that means that subsequent QMP or HMP commands must use
|
||||||
|
node-names for the image in question, as filename lookup
|
||||||
|
methods will fail.
|
||||||
|
|
||||||
|
If not specified, QEMU will automatically determine the
|
||||||
|
backing file string to use, or error out if there is no
|
||||||
|
obvious choice. Care should be taken when specifying the
|
||||||
|
string, to specify a valid filename or protocol.
|
||||||
|
(json-string, optional) (Since 2.1)
|
||||||
|
- "speed": the maximum speed, in bytes per second (json-int, optional)
|
||||||
|
- "on-error": the action to take on an error (default 'report'). 'stop' and
|
||||||
|
'enospc' can only be used if the block device supports io-status.
|
||||||
|
(json-string, optional) (Since 2.1)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
-> { "execute": "block-stream", "arguments": { "device": "virtio0",
|
||||||
|
"base": "/tmp/master.qcow2" } }
|
||||||
|
<- { "return": {} }
|
||||||
|
|
||||||
|
EQMP
|
||||||
|
|
||||||
{
|
{
|
||||||
.name = "block-commit",
|
.name = "block-commit",
|
||||||
.args_type = "device:B,base:s?,top:s?,backing-file:s?,speed:o?",
|
.args_type = "device:B,base:s?,top:s?,backing-file:s?,speed:o?",
|
||||||
|
@ -1073,7 +1110,7 @@ EQMP
|
||||||
{
|
{
|
||||||
.name = "drive-backup",
|
.name = "drive-backup",
|
||||||
.args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
|
.args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
|
||||||
"on-source-error:s?,on-target-error:s?",
|
"bitmap:s?,on-source-error:s?,on-target-error:s?",
|
||||||
.mhandler.cmd_new = qmp_marshal_input_drive_backup,
|
.mhandler.cmd_new = qmp_marshal_input_drive_backup,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1100,8 +1137,10 @@ Arguments:
|
||||||
(json-string, optional)
|
(json-string, optional)
|
||||||
- "sync": what parts of the disk image should be copied to the destination;
|
- "sync": what parts of the disk image should be copied to the destination;
|
||||||
possibilities include "full" for all the disk, "top" for only the sectors
|
possibilities include "full" for all the disk, "top" for only the sectors
|
||||||
allocated in the topmost image, or "none" to only replicate new I/O
|
allocated in the topmost image, "dirty-bitmap" for only the dirty sectors in
|
||||||
(MirrorSyncMode).
|
the bitmap, or "none" to only replicate new I/O (MirrorSyncMode).
|
||||||
|
- "bitmap": dirty bitmap name for sync==dirty-bitmap. Must be present if sync
|
||||||
|
is "dirty-bitmap", must NOT be present otherwise.
|
||||||
- "mode": whether and how QEMU should create a new image
|
- "mode": whether and how QEMU should create a new image
|
||||||
(NewImageMode, optional, default 'absolute-paths')
|
(NewImageMode, optional, default 'absolute-paths')
|
||||||
- "speed": the maximum speed, in bytes per second (json-int, optional)
|
- "speed": the maximum speed, in bytes per second (json-int, optional)
|
||||||
|
@ -1266,6 +1305,91 @@ Example:
|
||||||
"name": "snapshot0" } } ] } }
|
"name": "snapshot0" } } ] } }
|
||||||
<- { "return": {} }
|
<- { "return": {} }
|
||||||
|
|
||||||
|
EQMP
|
||||||
|
|
||||||
|
{
|
||||||
|
.name = "block-dirty-bitmap-add",
|
||||||
|
.args_type = "node:B,name:s,granularity:i?",
|
||||||
|
.mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_add,
|
||||||
|
},
|
||||||
|
|
||||||
|
SQMP
|
||||||
|
|
||||||
|
block-dirty-bitmap-add
|
||||||
|
----------------------
|
||||||
|
Since 2.4
|
||||||
|
|
||||||
|
Create a dirty bitmap with a name on the device, and start tracking the writes.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
- "node": device/node on which to create dirty bitmap (json-string)
|
||||||
|
- "name": name of the new dirty bitmap (json-string)
|
||||||
|
- "granularity": granularity to track writes with (int, optional)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
-> { "execute": "block-dirty-bitmap-add", "arguments": { "node": "drive0",
|
||||||
|
"name": "bitmap0" } }
|
||||||
|
<- { "return": {} }
|
||||||
|
|
||||||
|
EQMP
|
||||||
|
|
||||||
|
{
|
||||||
|
.name = "block-dirty-bitmap-remove",
|
||||||
|
.args_type = "node:B,name:s",
|
||||||
|
.mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_remove,
|
||||||
|
},
|
||||||
|
|
||||||
|
SQMP
|
||||||
|
|
||||||
|
block-dirty-bitmap-remove
|
||||||
|
-------------------------
|
||||||
|
Since 2.4
|
||||||
|
|
||||||
|
Stop write tracking and remove the dirty bitmap that was created with
|
||||||
|
block-dirty-bitmap-add.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
- "node": device/node on which to remove dirty bitmap (json-string)
|
||||||
|
- "name": name of the dirty bitmap to remove (json-string)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
-> { "execute": "block-dirty-bitmap-remove", "arguments": { "node": "drive0",
|
||||||
|
"name": "bitmap0" } }
|
||||||
|
<- { "return": {} }
|
||||||
|
|
||||||
|
EQMP
|
||||||
|
|
||||||
|
{
|
||||||
|
.name = "block-dirty-bitmap-clear",
|
||||||
|
.args_type = "node:B,name:s",
|
||||||
|
.mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_clear,
|
||||||
|
},
|
||||||
|
|
||||||
|
SQMP
|
||||||
|
|
||||||
|
block-dirty-bitmap-clear
|
||||||
|
------------------------
|
||||||
|
Since 2.4
|
||||||
|
|
||||||
|
Reset the dirty bitmap associated with a node so that an incremental backup
|
||||||
|
from this point in time forward will only backup clusters modified after this
|
||||||
|
clear operation.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
- "node": device/node on which to remove dirty bitmap (json-string)
|
||||||
|
- "name": name of the dirty bitmap to remove (json-string)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
-> { "execute": "block-dirty-bitmap-clear", "arguments": { "node": "drive0",
|
||||||
|
"name": "bitmap0" } }
|
||||||
|
<- { "return": {} }
|
||||||
|
|
||||||
EQMP
|
EQMP
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -2911,6 +2911,17 @@ sub process {
|
||||||
if ($rawline =~ /\b(?:Qemu|QEmu)\b/) {
|
if ($rawline =~ /\b(?:Qemu|QEmu)\b/) {
|
||||||
WARN("use QEMU instead of Qemu or QEmu\n" . $herecurr);
|
WARN("use QEMU instead of Qemu or QEmu\n" . $herecurr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# check for non-portable ffs() calls that have portable alternatives in QEMU
|
||||||
|
if ($line =~ /\bffs\(/) {
|
||||||
|
ERROR("use ctz32() instead of ffs()\n" . $herecurr);
|
||||||
|
}
|
||||||
|
if ($line =~ /\bffsl\(/) {
|
||||||
|
ERROR("use ctz32() or ctz64() instead of ffsl()\n" . $herecurr);
|
||||||
|
}
|
||||||
|
if ($line =~ /\bffsll\(/) {
|
||||||
|
ERROR("use ctz64() instead of ffsll()\n" . $herecurr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# If we have no input at all, then there is nothing to report on
|
# If we have no input at all, then there is nothing to report on
|
||||||
|
|
|
@ -22,12 +22,86 @@ def isnull(ptr):
|
||||||
def int128(p):
|
def int128(p):
|
||||||
return long(p['lo']) + (long(p['hi']) << 64)
|
return long(p['lo']) + (long(p['hi']) << 64)
|
||||||
|
|
||||||
|
def get_fs_base():
|
||||||
|
'''Fetch %fs base value using arch_prctl(ARCH_GET_FS)'''
|
||||||
|
# %rsp - 120 is scratch space according to the SystemV ABI
|
||||||
|
old = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)')
|
||||||
|
gdb.execute('call arch_prctl(0x1003, $rsp - 120)', False, True)
|
||||||
|
fs_base = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)')
|
||||||
|
gdb.execute('set *(uint64_t*)($rsp - 120) = %s' % old, False, True)
|
||||||
|
return fs_base
|
||||||
|
|
||||||
|
def get_glibc_pointer_guard():
|
||||||
|
'''Fetch glibc pointer guard value'''
|
||||||
|
fs_base = get_fs_base()
|
||||||
|
return gdb.parse_and_eval('*(uint64_t*)((uint64_t)%s + 0x30)' % fs_base)
|
||||||
|
|
||||||
|
def glibc_ptr_demangle(val, pointer_guard):
|
||||||
|
'''Undo effect of glibc's PTR_MANGLE()'''
|
||||||
|
return gdb.parse_and_eval('(((uint64_t)%s >> 0x11) | ((uint64_t)%s << (64 - 0x11))) ^ (uint64_t)%s' % (val, val, pointer_guard))
|
||||||
|
|
||||||
|
def bt_jmpbuf(jmpbuf):
|
||||||
|
'''Backtrace a jmpbuf'''
|
||||||
|
JB_RBX = 0
|
||||||
|
JB_RBP = 1
|
||||||
|
JB_R12 = 2
|
||||||
|
JB_R13 = 3
|
||||||
|
JB_R14 = 4
|
||||||
|
JB_R15 = 5
|
||||||
|
JB_RSP = 6
|
||||||
|
JB_PC = 7
|
||||||
|
|
||||||
|
old_rbx = gdb.parse_and_eval('(uint64_t)$rbx')
|
||||||
|
old_rbp = gdb.parse_and_eval('(uint64_t)$rbp')
|
||||||
|
old_rsp = gdb.parse_and_eval('(uint64_t)$rsp')
|
||||||
|
old_r12 = gdb.parse_and_eval('(uint64_t)$r12')
|
||||||
|
old_r13 = gdb.parse_and_eval('(uint64_t)$r13')
|
||||||
|
old_r14 = gdb.parse_and_eval('(uint64_t)$r14')
|
||||||
|
old_r15 = gdb.parse_and_eval('(uint64_t)$r15')
|
||||||
|
old_rip = gdb.parse_and_eval('(uint64_t)$rip')
|
||||||
|
|
||||||
|
pointer_guard = get_glibc_pointer_guard()
|
||||||
|
gdb.execute('set $rbx = %s' % jmpbuf[JB_RBX])
|
||||||
|
gdb.execute('set $rbp = %s' % glibc_ptr_demangle(jmpbuf[JB_RBP], pointer_guard))
|
||||||
|
gdb.execute('set $rsp = %s' % glibc_ptr_demangle(jmpbuf[JB_RSP], pointer_guard))
|
||||||
|
gdb.execute('set $r12 = %s' % jmpbuf[JB_R12])
|
||||||
|
gdb.execute('set $r13 = %s' % jmpbuf[JB_R13])
|
||||||
|
gdb.execute('set $r14 = %s' % jmpbuf[JB_R14])
|
||||||
|
gdb.execute('set $r15 = %s' % jmpbuf[JB_R15])
|
||||||
|
gdb.execute('set $rip = %s' % glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard))
|
||||||
|
|
||||||
|
gdb.execute('bt')
|
||||||
|
|
||||||
|
gdb.execute('set $rbx = %s' % old_rbx)
|
||||||
|
gdb.execute('set $rbp = %s' % old_rbp)
|
||||||
|
gdb.execute('set $rsp = %s' % old_rsp)
|
||||||
|
gdb.execute('set $r12 = %s' % old_r12)
|
||||||
|
gdb.execute('set $r13 = %s' % old_r13)
|
||||||
|
gdb.execute('set $r14 = %s' % old_r14)
|
||||||
|
gdb.execute('set $r15 = %s' % old_r15)
|
||||||
|
gdb.execute('set $rip = %s' % old_rip)
|
||||||
|
|
||||||
class QemuCommand(gdb.Command):
|
class QemuCommand(gdb.Command):
|
||||||
'''Prefix for QEMU debug support commands'''
|
'''Prefix for QEMU debug support commands'''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
gdb.Command.__init__(self, 'qemu', gdb.COMMAND_DATA,
|
gdb.Command.__init__(self, 'qemu', gdb.COMMAND_DATA,
|
||||||
gdb.COMPLETE_NONE, True)
|
gdb.COMPLETE_NONE, True)
|
||||||
|
|
||||||
|
class CoroutineCommand(gdb.Command):
|
||||||
|
'''Display coroutine backtrace'''
|
||||||
|
def __init__(self):
|
||||||
|
gdb.Command.__init__(self, 'qemu coroutine', gdb.COMMAND_DATA,
|
||||||
|
gdb.COMPLETE_NONE)
|
||||||
|
|
||||||
|
def invoke(self, arg, from_tty):
|
||||||
|
argv = gdb.string_to_argv(arg)
|
||||||
|
if len(argv) != 1:
|
||||||
|
gdb.write('usage: qemu coroutine <coroutine-pointer>\n')
|
||||||
|
return
|
||||||
|
|
||||||
|
coroutine_pointer = gdb.parse_and_eval(argv[0]).cast(gdb.lookup_type('CoroutineUContext').pointer())
|
||||||
|
bt_jmpbuf(coroutine_pointer['env']['__jmpbuf'])
|
||||||
|
|
||||||
class MtreeCommand(gdb.Command):
|
class MtreeCommand(gdb.Command):
|
||||||
'''Display the memory tree hierarchy'''
|
'''Display the memory tree hierarchy'''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -86,4 +160,5 @@ class MtreeCommand(gdb.Command):
|
||||||
subregion = subregion['subregions_link']['tqe_next']
|
subregion = subregion['subregions_link']['tqe_next']
|
||||||
|
|
||||||
QemuCommand()
|
QemuCommand()
|
||||||
|
CoroutineCommand()
|
||||||
MtreeCommand()
|
MtreeCommand()
|
||||||
|
|
|
@ -21,6 +21,9 @@ class QMPConnectError(QMPError):
|
||||||
class QMPCapabilitiesError(QMPError):
|
class QMPCapabilitiesError(QMPError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class QMPTimeoutError(QMPError):
|
||||||
|
pass
|
||||||
|
|
||||||
class QEMUMonitorProtocol:
|
class QEMUMonitorProtocol:
|
||||||
def __init__(self, address, server=False):
|
def __init__(self, address, server=False):
|
||||||
"""
|
"""
|
||||||
|
@ -72,6 +75,44 @@ class QEMUMonitorProtocol:
|
||||||
|
|
||||||
error = socket.error
|
error = socket.error
|
||||||
|
|
||||||
|
def __get_events(self, wait=False):
|
||||||
|
"""
|
||||||
|
Check for new events in the stream and cache them in __events.
|
||||||
|
|
||||||
|
@param wait (bool): block until an event is available.
|
||||||
|
@param wait (float): If wait is a float, treat it as a timeout value.
|
||||||
|
|
||||||
|
@raise QMPTimeoutError: If a timeout float is provided and the timeout
|
||||||
|
period elapses.
|
||||||
|
@raise QMPConnectError: If wait is True but no events could be retrieved
|
||||||
|
or if some other error occurred.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check for new events regardless and pull them into the cache:
|
||||||
|
self.__sock.setblocking(0)
|
||||||
|
try:
|
||||||
|
self.__json_read()
|
||||||
|
except socket.error, err:
|
||||||
|
if err[0] == errno.EAGAIN:
|
||||||
|
# No data available
|
||||||
|
pass
|
||||||
|
self.__sock.setblocking(1)
|
||||||
|
|
||||||
|
# Wait for new events, if needed.
|
||||||
|
# if wait is 0.0, this means "no wait" and is also implicitly false.
|
||||||
|
if not self.__events and wait:
|
||||||
|
if isinstance(wait, float):
|
||||||
|
self.__sock.settimeout(wait)
|
||||||
|
try:
|
||||||
|
ret = self.__json_read(only_event=True)
|
||||||
|
except socket.timeout:
|
||||||
|
raise QMPTimeoutError("Timeout waiting for event")
|
||||||
|
except:
|
||||||
|
raise QMPConnectError("Error while reading from socket")
|
||||||
|
if ret is None:
|
||||||
|
raise QMPConnectError("Error while reading from socket")
|
||||||
|
self.__sock.settimeout(None)
|
||||||
|
|
||||||
def connect(self, negotiate=True):
|
def connect(self, negotiate=True):
|
||||||
"""
|
"""
|
||||||
Connect to the QMP Monitor and perform capabilities negotiation.
|
Connect to the QMP Monitor and perform capabilities negotiation.
|
||||||
|
@ -140,43 +181,37 @@ class QEMUMonitorProtocol:
|
||||||
"""
|
"""
|
||||||
Get and delete the first available QMP event.
|
Get and delete the first available QMP event.
|
||||||
|
|
||||||
@param wait: block until an event is available (bool)
|
@param wait (bool): block until an event is available.
|
||||||
|
@param wait (float): If wait is a float, treat it as a timeout value.
|
||||||
|
|
||||||
|
@raise QMPTimeoutError: If a timeout float is provided and the timeout
|
||||||
|
period elapses.
|
||||||
|
@raise QMPConnectError: If wait is True but no events could be retrieved
|
||||||
|
or if some other error occurred.
|
||||||
|
|
||||||
|
@return The first available QMP event, or None.
|
||||||
"""
|
"""
|
||||||
self.__sock.setblocking(0)
|
self.__get_events(wait)
|
||||||
try:
|
|
||||||
self.__json_read()
|
if self.__events:
|
||||||
except socket.error, err:
|
return self.__events.pop(0)
|
||||||
if err[0] == errno.EAGAIN:
|
return None
|
||||||
# No data available
|
|
||||||
pass
|
|
||||||
self.__sock.setblocking(1)
|
|
||||||
if not self.__events and wait:
|
|
||||||
self.__json_read(only_event=True)
|
|
||||||
event = self.__events[0]
|
|
||||||
del self.__events[0]
|
|
||||||
return event
|
|
||||||
|
|
||||||
def get_events(self, wait=False):
|
def get_events(self, wait=False):
|
||||||
"""
|
"""
|
||||||
Get a list of available QMP events.
|
Get a list of available QMP events.
|
||||||
|
|
||||||
@param wait: block until an event is available (bool)
|
@param wait (bool): block until an event is available.
|
||||||
"""
|
@param wait (float): If wait is a float, treat it as a timeout value.
|
||||||
self.__sock.setblocking(0)
|
|
||||||
try:
|
|
||||||
self.__json_read()
|
|
||||||
except socket.error, err:
|
|
||||||
if err[0] == errno.EAGAIN:
|
|
||||||
# No data available
|
|
||||||
pass
|
|
||||||
self.__sock.setblocking(1)
|
|
||||||
if not self.__events and wait:
|
|
||||||
ret = self.__json_read(only_event=True)
|
|
||||||
if ret == None:
|
|
||||||
# We are in blocking mode, if don't get anything, something
|
|
||||||
# went wrong
|
|
||||||
raise QMPConnectError("Error while reading from socket")
|
|
||||||
|
|
||||||
|
@raise QMPTimeoutError: If a timeout float is provided and the timeout
|
||||||
|
period elapses.
|
||||||
|
@raise QMPConnectError: If wait is True but no events could be retrieved
|
||||||
|
or if some other error occurred.
|
||||||
|
|
||||||
|
@return The list of available QMP events.
|
||||||
|
"""
|
||||||
|
self.__get_events(wait)
|
||||||
return self.__events
|
return self.__events
|
||||||
|
|
||||||
def clear_events(self):
|
def clear_events(self):
|
||||||
|
|
|
@ -2251,8 +2251,8 @@ static inline ppcmas_tlb_t *booke206_get_tlbm(CPUPPCState *env, const int tlbn,
|
||||||
{
|
{
|
||||||
int r;
|
int r;
|
||||||
uint32_t ways = booke206_tlb_ways(env, tlbn);
|
uint32_t ways = booke206_tlb_ways(env, tlbn);
|
||||||
int ways_bits = ffs(ways) - 1;
|
int ways_bits = ctz32(ways);
|
||||||
int tlb_bits = ffs(booke206_tlb_size(env, tlbn)) - 1;
|
int tlb_bits = ctz32(booke206_tlb_size(env, tlbn));
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
way &= ways - 1;
|
way &= ways - 1;
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Test some qemu-img convert cases
|
||||||
|
#
|
||||||
|
# Copyright (C) 2015 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
# creator
|
||||||
|
owner=kwolf@redhat.com
|
||||||
|
|
||||||
|
seq="$(basename $0)"
|
||||||
|
echo "QA output created by $seq"
|
||||||
|
|
||||||
|
here="$PWD"
|
||||||
|
tmp=/tmp/$$
|
||||||
|
status=1 # failure is the default!
|
||||||
|
|
||||||
|
_cleanup()
|
||||||
|
{
|
||||||
|
rm -f "$TEST_IMG".[123]
|
||||||
|
_cleanup_test_img
|
||||||
|
}
|
||||||
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||||
|
|
||||||
|
# get standard environment, filters and checks
|
||||||
|
. ./common.rc
|
||||||
|
. ./common.filter
|
||||||
|
|
||||||
|
_supported_fmt qcow2
|
||||||
|
_supported_proto file
|
||||||
|
_supported_os Linux
|
||||||
|
|
||||||
|
|
||||||
|
TEST_IMG="$TEST_IMG".base _make_test_img 64M
|
||||||
|
$QEMU_IO -c "write -P 0x11 0 64M" "$TEST_IMG".base 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Check allocation status regression with -B ==="
|
||||||
|
echo
|
||||||
|
|
||||||
|
_make_test_img -b "$TEST_IMG".base
|
||||||
|
$QEMU_IO -c "write -P 0x22 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IMG map "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Check that zero clusters are kept in overlay ==="
|
||||||
|
echo
|
||||||
|
|
||||||
|
_make_test_img -b "$TEST_IMG".base
|
||||||
|
|
||||||
|
$QEMU_IO -c "write -P 0 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -c -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
$QEMU_IO -c "write -z 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -c -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Concatenate multiple source images ==="
|
||||||
|
echo
|
||||||
|
|
||||||
|
TEST_IMG="$TEST_IMG".1 _make_test_img 4M
|
||||||
|
TEST_IMG="$TEST_IMG".2 _make_test_img 4M
|
||||||
|
TEST_IMG="$TEST_IMG".3 _make_test_img 4M
|
||||||
|
|
||||||
|
$QEMU_IO -c "write -P 0x11 0 64k" "$TEST_IMG".1 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write -P 0x22 0 64k" "$TEST_IMG".2 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write -P 0x33 0 64k" "$TEST_IMG".3 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
$QEMU_IMG convert -O $IMGFMT "$TEST_IMG".[123] "$TEST_IMG"
|
||||||
|
$QEMU_IMG map "$TEST_IMG" | _filter_qemu_img_map
|
||||||
|
$QEMU_IO -c "read -P 0x11 0 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x22 4M 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x33 8M 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
$QEMU_IMG convert -c -O $IMGFMT "$TEST_IMG".[123] "$TEST_IMG"
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map
|
||||||
|
$QEMU_IO -c "read -P 0x11 0 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x22 4M 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x33 8M 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
# -B can't be combined with concatenation
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base "$TEST_IMG".[123] "$TEST_IMG"
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -c -B "$TEST_IMG".base "$TEST_IMG".[123] "$TEST_IMG"
|
||||||
|
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Compression with misaligned allocations and image sizes ==="
|
||||||
|
echo
|
||||||
|
|
||||||
|
TEST_IMG="$TEST_IMG".1 _make_test_img 1023k -o cluster_size=1024
|
||||||
|
TEST_IMG="$TEST_IMG".2 _make_test_img 1023k -o cluster_size=1024
|
||||||
|
|
||||||
|
$QEMU_IO -c "write -P 0x11 16k 16k" "$TEST_IMG".1 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write -P 0x22 130k 130k" "$TEST_IMG".1 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write -P 0x33 1022k 1k" "$TEST_IMG".1 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write -P 0x44 0k 1k" "$TEST_IMG".2 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
$QEMU_IMG convert -c -O $IMGFMT "$TEST_IMG".[12] "$TEST_IMG"
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map
|
||||||
|
$QEMU_IO -c "read -P 0 0k 16k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x11 16k 16k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 32k 98k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x22 130k 130k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 260k 762k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x33 1022k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x44 1023k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 1024k 1022k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Full allocation with -S 0 ==="
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Standalone image
|
||||||
|
_make_test_img 64M
|
||||||
|
$QEMU_IO -c "write -P 0x22 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write -P 0 3M 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo convert -S 0:
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -S 0 "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 3M 61M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo convert -c -S 0:
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -c -S 0 "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 3M 61M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
|
||||||
|
# With backing file
|
||||||
|
TEST_IMG="$TEST_IMG".base _make_test_img 64M
|
||||||
|
$QEMU_IO -c "write -P 0x11 0 32M" "$TEST_IMG".base 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
_make_test_img -b "$TEST_IMG".base 64M
|
||||||
|
$QEMU_IO -c "write -P 0x22 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo convert -S 0 with source backing file:
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -S 0 "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x11 3M 29M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 32M 32M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo convert -c -S 0 with source backing file:
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -c -S 0 "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x11 3M 29M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 32M 32M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
|
||||||
|
# With keeping the backing file
|
||||||
|
echo
|
||||||
|
echo convert -S 0 -B ...
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -S 0 "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x11 3M 29M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 32M 32M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo convert -c -S 0 -B ...
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -c -S 0 "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0x11 3M 29M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "read -P 0 32M 32M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Non-zero -S ==="
|
||||||
|
echo
|
||||||
|
|
||||||
|
_make_test_img 64M -o cluster_size=1k
|
||||||
|
$QEMU_IO -c "write -P 0 0 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write 0 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write 8k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
$QEMU_IO -c "write 17k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
|
for min_sparse in 4k 8k; do
|
||||||
|
echo
|
||||||
|
echo convert -S $min_sparse
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -o cluster_size=1k -S $min_sparse "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo convert -c -S $min_sparse
|
||||||
|
# For compressed images, -S values other than 0 are ignored
|
||||||
|
$QEMU_IMG convert -O $IMGFMT -o cluster_size=1k -c -S $min_sparse "$TEST_IMG" "$TEST_IMG".orig
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map
|
||||||
|
done
|
||||||
|
|
||||||
|
# success, all done
|
||||||
|
echo '*** done'
|
||||||
|
rm -f $seq.full
|
||||||
|
status=0
|
|
@ -0,0 +1,209 @@
|
||||||
|
QA output created by 122
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
|
||||||
|
wrote 67108864/67108864 bytes at offset 0
|
||||||
|
64 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
=== Check allocation status regression with -B ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base'
|
||||||
|
wrote 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
Offset Length File
|
||||||
|
0 0x300000 TEST_DIR/t.IMGFMT.orig
|
||||||
|
0x300000 0x3d00000 TEST_DIR/t.IMGFMT.base
|
||||||
|
|
||||||
|
=== Check that zero clusters are kept in overlay ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base'
|
||||||
|
wrote 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
=== Concatenate multiple source images ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.1', fmt=IMGFMT size=4194304
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.2', fmt=IMGFMT size=4194304
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.3', fmt=IMGFMT size=4194304
|
||||||
|
wrote 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
Offset Length File
|
||||||
|
0 0x10000 TEST_DIR/t.IMGFMT
|
||||||
|
0x400000 0x10000 TEST_DIR/t.IMGFMT
|
||||||
|
0x800000 0x10000 TEST_DIR/t.IMGFMT
|
||||||
|
read 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 65536/65536 bytes at offset 4194304
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 65536/65536 bytes at offset 8388608
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
[{ "start": 0, "length": 65536, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 65536, "length": 4128768, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 4194304, "length": 65536, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 4259840, "length": 4128768, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 8388608, "length": 65536, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 8454144, "length": 4128768, "depth": 0, "zero": true, "data": false}]
|
||||||
|
read 65536/65536 bytes at offset 0
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 65536/65536 bytes at offset 4194304
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 65536/65536 bytes at offset 8388608
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
qemu-img: -B makes no sense when concatenating multiple input images
|
||||||
|
qemu-img: -B makes no sense when concatenating multiple input images
|
||||||
|
|
||||||
|
=== Compression with misaligned allocations and image sizes ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.1', fmt=IMGFMT size=1047552
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.2', fmt=IMGFMT size=1047552
|
||||||
|
wrote 16384/16384 bytes at offset 16384
|
||||||
|
16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 133120/133120 bytes at offset 133120
|
||||||
|
130 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 1024/1024 bytes at offset 1046528
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 1024/1024 bytes at offset 0
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
[{ "start": 0, "length": 65536, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 65536, "length": 65536, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 131072, "length": 196608, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 327680, "length": 655360, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 983040, "length": 65536, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 1048576, "length": 1046528, "depth": 0, "zero": true, "data": false}]
|
||||||
|
read 16384/16384 bytes at offset 0
|
||||||
|
16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 16384/16384 bytes at offset 16384
|
||||||
|
16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 100352/100352 bytes at offset 32768
|
||||||
|
98 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 133120/133120 bytes at offset 133120
|
||||||
|
130 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 780288/780288 bytes at offset 266240
|
||||||
|
762 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 1024/1024 bytes at offset 1046528
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 1024/1024 bytes at offset 1047552
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 1046528/1046528 bytes at offset 1048576
|
||||||
|
1022 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
=== Full allocation with -S 0 ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
wrote 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 3145728/3145728 bytes at offset 3145728
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
convert -S 0:
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 63963136/63963136 bytes at offset 3145728
|
||||||
|
61 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
[{ "start": 0, "length": 6291456, "depth": 0, "zero": false, "data": true, "offset": 327680},
|
||||||
|
{ "start": 6291456, "length": 60817408, "depth": 0, "zero": true, "data": false}]
|
||||||
|
|
||||||
|
convert -c -S 0:
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 63963136/63963136 bytes at offset 3145728
|
||||||
|
61 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
[{ "start": 0, "length": 6291456, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 6291456, "length": 60817408, "depth": 0, "zero": true, "data": false}]
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
|
||||||
|
wrote 33554432/33554432 bytes at offset 0
|
||||||
|
32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base'
|
||||||
|
wrote 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
convert -S 0 with source backing file:
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 30408704/30408704 bytes at offset 3145728
|
||||||
|
29 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 33554432/33554432 bytes at offset 33554432
|
||||||
|
32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
[{ "start": 0, "length": 67108864, "depth": 0, "zero": false, "data": true, "offset": 327680}]
|
||||||
|
|
||||||
|
convert -c -S 0 with source backing file:
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 30408704/30408704 bytes at offset 3145728
|
||||||
|
29 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 33554432/33554432 bytes at offset 33554432
|
||||||
|
32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
[{ "start": 0, "length": 67108864, "depth": 0, "zero": false, "data": true}]
|
||||||
|
|
||||||
|
convert -S 0 -B ...
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 30408704/30408704 bytes at offset 3145728
|
||||||
|
29 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 33554432/33554432 bytes at offset 33554432
|
||||||
|
32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
[{ "start": 0, "length": 67108864, "depth": 0, "zero": false, "data": true, "offset": 327680}]
|
||||||
|
|
||||||
|
convert -c -S 0 -B ...
|
||||||
|
read 3145728/3145728 bytes at offset 0
|
||||||
|
3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 30408704/30408704 bytes at offset 3145728
|
||||||
|
29 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 33554432/33554432 bytes at offset 33554432
|
||||||
|
32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
[{ "start": 0, "length": 67108864, "depth": 0, "zero": false, "data": true}]
|
||||||
|
|
||||||
|
=== Non-zero -S ===
|
||||||
|
|
||||||
|
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)
|
||||||
|
wrote 1024/1024 bytes at offset 0
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 1024/1024 bytes at offset 8192
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 1024/1024 bytes at offset 17408
|
||||||
|
1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
convert -S 4k
|
||||||
|
[{ "start": 0, "length": 1024, "depth": 0, "zero": false, "data": true, "offset": 8192},
|
||||||
|
{ "start": 1024, "length": 7168, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 8192, "length": 1024, "depth": 0, "zero": false, "data": true, "offset": 9216},
|
||||||
|
{ "start": 9216, "length": 8192, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 17408, "length": 1024, "depth": 0, "zero": false, "data": true, "offset": 10240},
|
||||||
|
{ "start": 18432, "length": 67090432, "depth": 0, "zero": true, "data": false}]
|
||||||
|
|
||||||
|
convert -c -S 4k
|
||||||
|
[{ "start": 0, "length": 1024, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 1024, "length": 7168, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 8192, "length": 1024, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 9216, "length": 8192, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 17408, "length": 1024, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 18432, "length": 67090432, "depth": 0, "zero": true, "data": false}]
|
||||||
|
|
||||||
|
convert -S 8k
|
||||||
|
[{ "start": 0, "length": 9216, "depth": 0, "zero": false, "data": true, "offset": 8192},
|
||||||
|
{ "start": 9216, "length": 8192, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 17408, "length": 1024, "depth": 0, "zero": false, "data": true, "offset": 17408},
|
||||||
|
{ "start": 18432, "length": 67090432, "depth": 0, "zero": true, "data": false}]
|
||||||
|
|
||||||
|
convert -c -S 8k
|
||||||
|
[{ "start": 0, "length": 1024, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 1024, "length": 7168, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 8192, "length": 1024, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 9216, "length": 8192, "depth": 0, "zero": true, "data": false},
|
||||||
|
{ "start": 17408, "length": 1024, "depth": 0, "zero": false, "data": true},
|
||||||
|
{ "start": 18432, "length": 67090432, "depth": 0, "zero": true, "data": false}]
|
||||||
|
*** done
|
|
@ -0,0 +1,363 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Tests for incremental drive-backup
|
||||||
|
#
|
||||||
|
# Copyright (C) 2015 John Snow for Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Based on 056.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import iotests
|
||||||
|
|
||||||
|
|
||||||
|
def io_write_patterns(img, patterns):
|
||||||
|
for pattern in patterns:
|
||||||
|
iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
|
||||||
|
|
||||||
|
|
||||||
|
def try_remove(img):
|
||||||
|
try:
|
||||||
|
os.remove(img)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Bitmap:
|
||||||
|
def __init__(self, name, drive):
|
||||||
|
self.name = name
|
||||||
|
self.drive = drive
|
||||||
|
self.num = 0
|
||||||
|
self.backups = list()
|
||||||
|
|
||||||
|
def base_target(self):
|
||||||
|
return (self.drive['backup'], None)
|
||||||
|
|
||||||
|
def new_target(self, num=None):
|
||||||
|
if num is None:
|
||||||
|
num = self.num
|
||||||
|
self.num = num + 1
|
||||||
|
base = os.path.join(iotests.test_dir,
|
||||||
|
"%s.%s." % (self.drive['id'], self.name))
|
||||||
|
suff = "%i.%s" % (num, self.drive['fmt'])
|
||||||
|
target = base + "inc" + suff
|
||||||
|
reference = base + "ref" + suff
|
||||||
|
self.backups.append((target, reference))
|
||||||
|
return (target, reference)
|
||||||
|
|
||||||
|
def last_target(self):
|
||||||
|
if self.backups:
|
||||||
|
return self.backups[-1]
|
||||||
|
return self.base_target()
|
||||||
|
|
||||||
|
def del_target(self):
|
||||||
|
for image in self.backups.pop():
|
||||||
|
try_remove(image)
|
||||||
|
self.num -= 1
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
for backup in self.backups:
|
||||||
|
for image in backup:
|
||||||
|
try_remove(image)
|
||||||
|
|
||||||
|
|
||||||
|
class TestIncrementalBackup(iotests.QMPTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.bitmaps = list()
|
||||||
|
self.files = list()
|
||||||
|
self.drives = list()
|
||||||
|
self.vm = iotests.VM()
|
||||||
|
self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)
|
||||||
|
|
||||||
|
# Create a base image with a distinctive patterning
|
||||||
|
drive0 = self.add_node('drive0')
|
||||||
|
self.img_create(drive0['file'], drive0['fmt'])
|
||||||
|
self.vm.add_drive(drive0['file'])
|
||||||
|
io_write_patterns(drive0['file'], (('0x41', 0, 512),
|
||||||
|
('0xd5', '1M', '32k'),
|
||||||
|
('0xdc', '32M', '124k')))
|
||||||
|
self.vm.launch()
|
||||||
|
|
||||||
|
|
||||||
|
def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None):
|
||||||
|
if path is None:
|
||||||
|
path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt))
|
||||||
|
if backup is None:
|
||||||
|
backup = os.path.join(iotests.test_dir,
|
||||||
|
'%s.full.backup.%s' % (node_id, fmt))
|
||||||
|
|
||||||
|
self.drives.append({
|
||||||
|
'id': node_id,
|
||||||
|
'file': path,
|
||||||
|
'backup': backup,
|
||||||
|
'fmt': fmt })
|
||||||
|
return self.drives[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def img_create(self, img, fmt=iotests.imgfmt, size='64M',
|
||||||
|
parent=None, parentFormat=None):
|
||||||
|
if parent:
|
||||||
|
if parentFormat is None:
|
||||||
|
parentFormat = fmt
|
||||||
|
iotests.qemu_img('create', '-f', fmt, img, size,
|
||||||
|
'-b', parent, '-F', parentFormat)
|
||||||
|
else:
|
||||||
|
iotests.qemu_img('create', '-f', fmt, img, size)
|
||||||
|
self.files.append(img)
|
||||||
|
|
||||||
|
|
||||||
|
def do_qmp_backup(self, error='Input/output error', **kwargs):
|
||||||
|
res = self.vm.qmp('drive-backup', **kwargs)
|
||||||
|
self.assert_qmp(res, 'return', {})
|
||||||
|
|
||||||
|
event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
|
||||||
|
match={'data': {'device': kwargs['device']}})
|
||||||
|
self.assertIsNotNone(event)
|
||||||
|
|
||||||
|
try:
|
||||||
|
failure = self.dictpath(event, 'data/error')
|
||||||
|
except AssertionError:
|
||||||
|
# Backup succeeded.
|
||||||
|
self.assert_qmp(event, 'data/offset', event['data']['len'])
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# Backup failed.
|
||||||
|
self.assert_qmp(event, 'data/error', error)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_anchor_backup(self, drive=None):
|
||||||
|
if drive is None:
|
||||||
|
drive = self.drives[-1]
|
||||||
|
res = self.do_qmp_backup(device=drive['id'], sync='full',
|
||||||
|
format=drive['fmt'], target=drive['backup'])
|
||||||
|
self.assertTrue(res)
|
||||||
|
self.files.append(drive['backup'])
|
||||||
|
return drive['backup']
|
||||||
|
|
||||||
|
|
||||||
|
def make_reference_backup(self, bitmap=None):
|
||||||
|
if bitmap is None:
|
||||||
|
bitmap = self.bitmaps[-1]
|
||||||
|
_, reference = bitmap.last_target()
|
||||||
|
res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full',
|
||||||
|
format=bitmap.drive['fmt'], target=reference)
|
||||||
|
self.assertTrue(res)
|
||||||
|
|
||||||
|
|
||||||
|
def add_bitmap(self, name, drive, **kwargs):
|
||||||
|
bitmap = Bitmap(name, drive)
|
||||||
|
self.bitmaps.append(bitmap)
|
||||||
|
result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
|
||||||
|
name=bitmap.name, **kwargs)
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
return bitmap
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_backup(self, bitmap=None, parent=None):
|
||||||
|
if bitmap is None:
|
||||||
|
bitmap = self.bitmaps[-1]
|
||||||
|
if parent is None:
|
||||||
|
parent, _ = bitmap.last_target()
|
||||||
|
|
||||||
|
target, _ = bitmap.new_target()
|
||||||
|
self.img_create(target, bitmap.drive['fmt'], parent=parent)
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def create_incremental(self, bitmap=None, parent=None,
|
||||||
|
parentFormat=None, validate=True):
|
||||||
|
if bitmap is None:
|
||||||
|
bitmap = self.bitmaps[-1]
|
||||||
|
if parent is None:
|
||||||
|
parent, _ = bitmap.last_target()
|
||||||
|
|
||||||
|
target = self.prepare_backup(bitmap, parent)
|
||||||
|
res = self.do_qmp_backup(device=bitmap.drive['id'],
|
||||||
|
sync='dirty-bitmap', bitmap=bitmap.name,
|
||||||
|
format=bitmap.drive['fmt'], target=target,
|
||||||
|
mode='existing')
|
||||||
|
if not res:
|
||||||
|
bitmap.del_target();
|
||||||
|
self.assertFalse(validate)
|
||||||
|
else:
|
||||||
|
self.make_reference_backup(bitmap)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def check_backups(self):
|
||||||
|
for bitmap in self.bitmaps:
|
||||||
|
for incremental, reference in bitmap.backups:
|
||||||
|
self.assertTrue(iotests.compare_images(incremental, reference))
|
||||||
|
last = bitmap.last_target()[0]
|
||||||
|
self.assertTrue(iotests.compare_images(last, bitmap.drive['file']))
|
||||||
|
|
||||||
|
|
||||||
|
def hmp_io_writes(self, drive, patterns):
|
||||||
|
for pattern in patterns:
|
||||||
|
self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
|
||||||
|
self.vm.hmp_qemu_io(drive, 'flush')
|
||||||
|
|
||||||
|
|
||||||
|
def do_incremental_simple(self, **kwargs):
|
||||||
|
self.create_anchor_backup()
|
||||||
|
self.add_bitmap('bitmap0', self.drives[0], **kwargs)
|
||||||
|
|
||||||
|
# Sanity: Create a "hollow" incremental backup
|
||||||
|
self.create_incremental()
|
||||||
|
# Three writes: One complete overwrite, one new segment,
|
||||||
|
# and one partial overlap.
|
||||||
|
self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
|
||||||
|
('0xfe', '16M', '256k'),
|
||||||
|
('0x64', '32736k', '64k')))
|
||||||
|
self.create_incremental()
|
||||||
|
# Three more writes, one of each kind, like above
|
||||||
|
self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
|
||||||
|
('0x55', '8M', '352k'),
|
||||||
|
('0x78', '15872k', '1M')))
|
||||||
|
self.create_incremental()
|
||||||
|
self.vm.shutdown()
|
||||||
|
self.check_backups()
|
||||||
|
|
||||||
|
|
||||||
|
def test_incremental_simple(self):
|
||||||
|
'''
|
||||||
|
Test: Create and verify three incremental backups.
|
||||||
|
|
||||||
|
Create a bitmap and a full backup before VM execution begins,
|
||||||
|
then create a series of three incremental backups "during execution,"
|
||||||
|
i.e.; after IO requests begin modifying the drive.
|
||||||
|
'''
|
||||||
|
return self.do_incremental_simple()
|
||||||
|
|
||||||
|
|
||||||
|
def test_small_granularity(self):
|
||||||
|
'''
|
||||||
|
Test: Create and verify backups made with a small granularity bitmap.
|
||||||
|
|
||||||
|
Perform the same test as test_incremental_simple, but with a granularity
|
||||||
|
of only 32KiB instead of the present default of 64KiB.
|
||||||
|
'''
|
||||||
|
return self.do_incremental_simple(granularity=32768)
|
||||||
|
|
||||||
|
|
||||||
|
def test_large_granularity(self):
|
||||||
|
'''
|
||||||
|
Test: Create and verify backups made with a large granularity bitmap.
|
||||||
|
|
||||||
|
Perform the same test as test_incremental_simple, but with a granularity
|
||||||
|
of 128KiB instead of the present default of 64KiB.
|
||||||
|
'''
|
||||||
|
return self.do_incremental_simple(granularity=131072)
|
||||||
|
|
||||||
|
|
||||||
|
def test_incremental_failure(self):
|
||||||
|
'''Test: Verify backups made after a failure are correct.
|
||||||
|
|
||||||
|
Simulate a failure during an incremental backup block job,
|
||||||
|
emulate additional writes, then create another incremental backup
|
||||||
|
afterwards and verify that the backup created is correct.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Create a blkdebug interface to this img as 'drive1',
|
||||||
|
# but don't actually create a new image.
|
||||||
|
drive1 = self.add_node('drive1', self.drives[0]['fmt'],
|
||||||
|
path=self.drives[0]['file'],
|
||||||
|
backup=self.drives[0]['backup'])
|
||||||
|
result = self.vm.qmp('blockdev-add', options={
|
||||||
|
'id': drive1['id'],
|
||||||
|
'driver': drive1['fmt'],
|
||||||
|
'file': {
|
||||||
|
'driver': 'blkdebug',
|
||||||
|
'image': {
|
||||||
|
'driver': 'file',
|
||||||
|
'filename': drive1['file']
|
||||||
|
},
|
||||||
|
'set-state': [{
|
||||||
|
'event': 'flush_to_disk',
|
||||||
|
'state': 1,
|
||||||
|
'new_state': 2
|
||||||
|
}],
|
||||||
|
'inject-error': [{
|
||||||
|
'event': 'read_aio',
|
||||||
|
'errno': 5,
|
||||||
|
'state': 2,
|
||||||
|
'immediately': False,
|
||||||
|
'once': True
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
self.create_anchor_backup(self.drives[0])
|
||||||
|
self.add_bitmap('bitmap0', drive1)
|
||||||
|
# Note: at this point, during a normal execution,
|
||||||
|
# Assume that the VM resumes and begins issuing IO requests here.
|
||||||
|
|
||||||
|
self.hmp_io_writes(drive1['id'], (('0xab', 0, 512),
|
||||||
|
('0xfe', '16M', '256k'),
|
||||||
|
('0x64', '32736k', '64k')))
|
||||||
|
|
||||||
|
result = self.create_incremental(validate=False)
|
||||||
|
self.assertFalse(result)
|
||||||
|
self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512),
|
||||||
|
('0x55', '8M', '352k'),
|
||||||
|
('0x78', '15872k', '1M')))
|
||||||
|
self.create_incremental()
|
||||||
|
self.vm.shutdown()
|
||||||
|
self.check_backups()
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_dirty_bitmap_missing(self):
|
||||||
|
self.assert_no_active_block_jobs()
|
||||||
|
self.files.append(self.err_img)
|
||||||
|
result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
|
||||||
|
sync='dirty-bitmap', format=self.drives[0]['fmt'],
|
||||||
|
target=self.err_img)
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_dirty_bitmap_not_found(self):
|
||||||
|
self.assert_no_active_block_jobs()
|
||||||
|
self.files.append(self.err_img)
|
||||||
|
result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
|
||||||
|
sync='dirty-bitmap', bitmap='unknown',
|
||||||
|
format=self.drives[0]['fmt'], target=self.err_img)
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_dirty_bitmap_bad_granularity(self):
|
||||||
|
'''
|
||||||
|
Test: Test what happens if we provide an improper granularity.
|
||||||
|
|
||||||
|
The granularity must always be a power of 2.
|
||||||
|
'''
|
||||||
|
self.assert_no_active_block_jobs()
|
||||||
|
self.assertRaises(AssertionError, self.add_bitmap,
|
||||||
|
'bitmap0', self.drives[0],
|
||||||
|
granularity=64000)
|
||||||
|
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.vm.shutdown()
|
||||||
|
for bitmap in self.bitmaps:
|
||||||
|
bitmap.cleanup()
|
||||||
|
for filename in self.files:
|
||||||
|
try_remove(filename)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
iotests.main(supported_fmts=['qcow2'])
|
|
@ -0,0 +1,5 @@
|
||||||
|
.......
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 7 tests
|
||||||
|
|
||||||
|
OK
|
|
@ -0,0 +1,86 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Tests that "bdrv_drain_all" doesn't drain block jobs
|
||||||
|
#
|
||||||
|
# Copyright (C) 2015 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import iotests
|
||||||
|
import time
|
||||||
|
|
||||||
|
class TestStopWithBlockJob(iotests.QMPTestCase):
|
||||||
|
test_img = os.path.join(iotests.test_dir, 'test.img')
|
||||||
|
target_img = os.path.join(iotests.test_dir, 'target.img')
|
||||||
|
base_img = os.path.join(iotests.test_dir, 'base.img')
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
iotests.qemu_img('create', '-f', iotests.imgfmt, self.base_img, "1G")
|
||||||
|
iotests.qemu_img('create', '-f', iotests.imgfmt, self.test_img, "-b", self.base_img)
|
||||||
|
iotests.qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x5d 1M 128M', self.test_img)
|
||||||
|
self.vm = iotests.VM().add_drive(self.test_img)
|
||||||
|
self.vm.launch()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
params = {"device": "drive0",
|
||||||
|
"bps": 0,
|
||||||
|
"bps_rd": 0,
|
||||||
|
"bps_wr": 0,
|
||||||
|
"iops": 0,
|
||||||
|
"iops_rd": 0,
|
||||||
|
"iops_wr": 0,
|
||||||
|
}
|
||||||
|
result = self.vm.qmp("block_set_io_throttle", conv_keys=False,
|
||||||
|
**params)
|
||||||
|
self.vm.shutdown()
|
||||||
|
|
||||||
|
def do_test_stop(self, cmd, **args):
|
||||||
|
"""Test 'stop' while block job is running on a throttled drive.
|
||||||
|
The 'stop' command shouldn't drain the job"""
|
||||||
|
params = {"device": "drive0",
|
||||||
|
"bps": 1024,
|
||||||
|
"bps_rd": 0,
|
||||||
|
"bps_wr": 0,
|
||||||
|
"iops": 0,
|
||||||
|
"iops_rd": 0,
|
||||||
|
"iops_wr": 0,
|
||||||
|
}
|
||||||
|
result = self.vm.qmp("block_set_io_throttle", conv_keys=False,
|
||||||
|
**params)
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
result = self.vm.qmp(cmd, **args)
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
result = self.vm.qmp("stop")
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
result = self.vm.qmp("query-block-jobs")
|
||||||
|
self.assert_qmp(result, 'return[0]/busy', True)
|
||||||
|
self.assert_qmp(result, 'return[0]/ready', False)
|
||||||
|
|
||||||
|
def test_drive_mirror(self):
|
||||||
|
self.do_test_stop("drive-mirror", device="drive0",
|
||||||
|
target=self.target_img,
|
||||||
|
sync="full")
|
||||||
|
|
||||||
|
def test_drive_backup(self):
|
||||||
|
self.do_test_stop("drive-backup", device="drive0",
|
||||||
|
target=self.target_img,
|
||||||
|
sync="full")
|
||||||
|
|
||||||
|
def test_block_commit(self):
|
||||||
|
self.do_test_stop("block-commit", device="drive0")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
iotests.main(supported_fmts=["qcow2"])
|
|
@ -0,0 +1,5 @@
|
||||||
|
...
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 3 tests
|
||||||
|
|
||||||
|
OK
|
|
@ -122,6 +122,9 @@
|
||||||
115 rw auto
|
115 rw auto
|
||||||
116 rw auto quick
|
116 rw auto quick
|
||||||
121 rw auto
|
121 rw auto
|
||||||
|
122 rw auto
|
||||||
123 rw auto quick
|
123 rw auto quick
|
||||||
|
124 rw auto backing
|
||||||
128 rw auto quick
|
128 rw auto quick
|
||||||
|
129 rw auto quick
|
||||||
130 rw auto quick
|
130 rw auto quick
|
||||||
|
|
|
@ -78,6 +78,23 @@ def create_image(name, size):
|
||||||
i = i + 512
|
i = i + 512
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
|
# Test if 'match' is a recursive subset of 'event'
|
||||||
|
def event_match(event, match=None):
|
||||||
|
if match is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for key in match:
|
||||||
|
if key in event:
|
||||||
|
if isinstance(event[key], dict):
|
||||||
|
if not event_match(event[key], match[key]):
|
||||||
|
return False
|
||||||
|
elif event[key] != match[key]:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
class VM(object):
|
class VM(object):
|
||||||
'''A QEMU VM'''
|
'''A QEMU VM'''
|
||||||
|
|
||||||
|
@ -92,6 +109,7 @@ class VM(object):
|
||||||
'-machine', 'accel=qtest',
|
'-machine', 'accel=qtest',
|
||||||
'-display', 'none', '-vga', 'none']
|
'-display', 'none', '-vga', 'none']
|
||||||
self._num_drives = 0
|
self._num_drives = 0
|
||||||
|
self._events = []
|
||||||
|
|
||||||
# This can be used to add an unused monitor instance.
|
# This can be used to add an unused monitor instance.
|
||||||
def add_monitor_telnet(self, ip, port):
|
def add_monitor_telnet(self, ip, port):
|
||||||
|
@ -202,14 +220,34 @@ class VM(object):
|
||||||
|
|
||||||
def get_qmp_event(self, wait=False):
|
def get_qmp_event(self, wait=False):
|
||||||
'''Poll for one queued QMP events and return it'''
|
'''Poll for one queued QMP events and return it'''
|
||||||
|
if len(self._events) > 0:
|
||||||
|
return self._events.pop(0)
|
||||||
return self._qmp.pull_event(wait=wait)
|
return self._qmp.pull_event(wait=wait)
|
||||||
|
|
||||||
def get_qmp_events(self, wait=False):
|
def get_qmp_events(self, wait=False):
|
||||||
'''Poll for queued QMP events and return a list of dicts'''
|
'''Poll for queued QMP events and return a list of dicts'''
|
||||||
events = self._qmp.get_events(wait=wait)
|
events = self._qmp.get_events(wait=wait)
|
||||||
|
events.extend(self._events)
|
||||||
|
del self._events[:]
|
||||||
self._qmp.clear_events()
|
self._qmp.clear_events()
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
def event_wait(self, name='BLOCK_JOB_COMPLETED', timeout=60.0, match=None):
|
||||||
|
# Search cached events
|
||||||
|
for event in self._events:
|
||||||
|
if (event['event'] == name) and event_match(event, match):
|
||||||
|
self._events.remove(event)
|
||||||
|
return event
|
||||||
|
|
||||||
|
# Poll for new events
|
||||||
|
while True:
|
||||||
|
event = self._qmp.pull_event(wait=timeout)
|
||||||
|
if (event['event'] == name) and event_match(event, match):
|
||||||
|
return event
|
||||||
|
self._events.append(event)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
|
index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
|
||||||
|
|
||||||
class QMPTestCase(unittest.TestCase):
|
class QMPTestCase(unittest.TestCase):
|
||||||
|
|
|
@ -107,6 +107,7 @@ static void test_notify(void)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
QemuMutex start_lock;
|
QemuMutex start_lock;
|
||||||
|
EventNotifier notifier;
|
||||||
bool thread_acquired;
|
bool thread_acquired;
|
||||||
} AcquireTestData;
|
} AcquireTestData;
|
||||||
|
|
||||||
|
@ -118,6 +119,8 @@ static void *test_acquire_thread(void *opaque)
|
||||||
qemu_mutex_lock(&data->start_lock);
|
qemu_mutex_lock(&data->start_lock);
|
||||||
qemu_mutex_unlock(&data->start_lock);
|
qemu_mutex_unlock(&data->start_lock);
|
||||||
|
|
||||||
|
g_usleep(500000);
|
||||||
|
event_notifier_set(&data->notifier);
|
||||||
aio_context_acquire(ctx);
|
aio_context_acquire(ctx);
|
||||||
aio_context_release(ctx);
|
aio_context_release(ctx);
|
||||||
|
|
||||||
|
@ -126,20 +129,19 @@ static void *test_acquire_thread(void *opaque)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dummy_notifier_read(EventNotifier *unused)
|
static void dummy_notifier_read(EventNotifier *n)
|
||||||
{
|
{
|
||||||
g_assert(false); /* should never be invoked */
|
event_notifier_test_and_clear(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_acquire(void)
|
static void test_acquire(void)
|
||||||
{
|
{
|
||||||
QemuThread thread;
|
QemuThread thread;
|
||||||
EventNotifier notifier;
|
|
||||||
AcquireTestData data;
|
AcquireTestData data;
|
||||||
|
|
||||||
/* Dummy event notifier ensures aio_poll() will block */
|
/* Dummy event notifier ensures aio_poll() will block */
|
||||||
event_notifier_init(¬ifier, false);
|
event_notifier_init(&data.notifier, false);
|
||||||
aio_set_event_notifier(ctx, ¬ifier, dummy_notifier_read);
|
aio_set_event_notifier(ctx, &data.notifier, dummy_notifier_read);
|
||||||
g_assert(!aio_poll(ctx, false)); /* consume aio_notify() */
|
g_assert(!aio_poll(ctx, false)); /* consume aio_notify() */
|
||||||
|
|
||||||
qemu_mutex_init(&data.start_lock);
|
qemu_mutex_init(&data.start_lock);
|
||||||
|
@ -153,12 +155,13 @@ static void test_acquire(void)
|
||||||
/* Block in aio_poll(), let other thread kick us and acquire context */
|
/* Block in aio_poll(), let other thread kick us and acquire context */
|
||||||
aio_context_acquire(ctx);
|
aio_context_acquire(ctx);
|
||||||
qemu_mutex_unlock(&data.start_lock); /* let the thread run */
|
qemu_mutex_unlock(&data.start_lock); /* let the thread run */
|
||||||
g_assert(!aio_poll(ctx, true));
|
g_assert(aio_poll(ctx, true));
|
||||||
|
g_assert(!data.thread_acquired);
|
||||||
aio_context_release(ctx);
|
aio_context_release(ctx);
|
||||||
|
|
||||||
qemu_thread_join(&thread);
|
qemu_thread_join(&thread);
|
||||||
aio_set_event_notifier(ctx, ¬ifier, NULL);
|
aio_set_event_notifier(ctx, &data.notifier, NULL);
|
||||||
event_notifier_cleanup(¬ifier);
|
event_notifier_cleanup(&data.notifier);
|
||||||
|
|
||||||
g_assert(data.thread_acquired);
|
g_assert(data.thread_acquired);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include "qemu/hbitmap.h"
|
#include "qemu/hbitmap.h"
|
||||||
|
|
||||||
#define LOG_BITS_PER_LONG (BITS_PER_LONG == 32 ? 5 : 6)
|
#define LOG_BITS_PER_LONG (BITS_PER_LONG == 32 ? 5 : 6)
|
||||||
|
@ -23,6 +25,7 @@ typedef struct TestHBitmapData {
|
||||||
HBitmap *hb;
|
HBitmap *hb;
|
||||||
unsigned long *bits;
|
unsigned long *bits;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
size_t old_size;
|
||||||
int granularity;
|
int granularity;
|
||||||
} TestHBitmapData;
|
} TestHBitmapData;
|
||||||
|
|
||||||
|
@ -91,6 +94,44 @@ static void hbitmap_test_init(TestHBitmapData *data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline size_t hbitmap_test_array_size(size_t bits)
|
||||||
|
{
|
||||||
|
size_t n = (bits + BITS_PER_LONG - 1) / BITS_PER_LONG;
|
||||||
|
return n ? n : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hbitmap_test_truncate_impl(TestHBitmapData *data,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
size_t n;
|
||||||
|
size_t m;
|
||||||
|
data->old_size = data->size;
|
||||||
|
data->size = size;
|
||||||
|
|
||||||
|
if (data->size == data->old_size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
n = hbitmap_test_array_size(size);
|
||||||
|
m = hbitmap_test_array_size(data->old_size);
|
||||||
|
data->bits = g_realloc(data->bits, sizeof(unsigned long) * n);
|
||||||
|
if (n > m) {
|
||||||
|
memset(&data->bits[m], 0x00, sizeof(unsigned long) * (n - m));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we shrink to an uneven multiple of sizeof(unsigned long),
|
||||||
|
* scrub the leftover memory. */
|
||||||
|
if (data->size < data->old_size) {
|
||||||
|
m = size % (sizeof(unsigned long) * 8);
|
||||||
|
if (m) {
|
||||||
|
unsigned long mask = (1ULL << m) - 1;
|
||||||
|
data->bits[n-1] &= mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hbitmap_truncate(data->hb, size);
|
||||||
|
}
|
||||||
|
|
||||||
static void hbitmap_test_teardown(TestHBitmapData *data,
|
static void hbitmap_test_teardown(TestHBitmapData *data,
|
||||||
const void *unused)
|
const void *unused)
|
||||||
{
|
{
|
||||||
|
@ -369,6 +410,198 @@ static void test_hbitmap_iter_granularity(TestHBitmapData *data,
|
||||||
g_assert_cmpint(hbitmap_iter_next(&hbi), <, 0);
|
g_assert_cmpint(hbitmap_iter_next(&hbi), <, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void hbitmap_test_set_boundary_bits(TestHBitmapData *data, ssize_t diff)
|
||||||
|
{
|
||||||
|
size_t size = data->size;
|
||||||
|
|
||||||
|
/* First bit */
|
||||||
|
hbitmap_test_set(data, 0, 1);
|
||||||
|
if (diff < 0) {
|
||||||
|
/* Last bit in new, shortened map */
|
||||||
|
hbitmap_test_set(data, size + diff - 1, 1);
|
||||||
|
|
||||||
|
/* First bit to be truncated away */
|
||||||
|
hbitmap_test_set(data, size + diff, 1);
|
||||||
|
}
|
||||||
|
/* Last bit */
|
||||||
|
hbitmap_test_set(data, size - 1, 1);
|
||||||
|
if (data->granularity == 0) {
|
||||||
|
hbitmap_test_check_get(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hbitmap_test_check_boundary_bits(TestHBitmapData *data)
|
||||||
|
{
|
||||||
|
size_t size = MIN(data->size, data->old_size);
|
||||||
|
|
||||||
|
if (data->granularity == 0) {
|
||||||
|
hbitmap_test_check_get(data);
|
||||||
|
hbitmap_test_check(data, 0);
|
||||||
|
} else {
|
||||||
|
/* If a granularity was set, note that every distinct
|
||||||
|
* (bit >> granularity) value that was set will increase
|
||||||
|
* the bit pop count by 2^granularity, not just 1.
|
||||||
|
*
|
||||||
|
* The hbitmap_test_check facility does not currently tolerate
|
||||||
|
* non-zero granularities, so test the boundaries and the population
|
||||||
|
* count manually.
|
||||||
|
*/
|
||||||
|
g_assert(hbitmap_get(data->hb, 0));
|
||||||
|
g_assert(hbitmap_get(data->hb, size - 1));
|
||||||
|
g_assert_cmpint(2 << data->granularity, ==, hbitmap_count(data->hb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generic truncate test. */
|
||||||
|
static void hbitmap_test_truncate(TestHBitmapData *data,
|
||||||
|
size_t size,
|
||||||
|
ssize_t diff,
|
||||||
|
int granularity)
|
||||||
|
{
|
||||||
|
hbitmap_test_init(data, size, granularity);
|
||||||
|
hbitmap_test_set_boundary_bits(data, diff);
|
||||||
|
hbitmap_test_truncate_impl(data, size + diff);
|
||||||
|
hbitmap_test_check_boundary_bits(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_hbitmap_truncate_nop(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
hbitmap_test_truncate(data, L2, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grow by an amount smaller than the granularity, without crossing
|
||||||
|
* a granularity alignment boundary. Effectively a NOP.
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_grow_negligible(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2 - 1;
|
||||||
|
size_t diff = 1;
|
||||||
|
int granularity = 1;
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, diff, granularity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrink by an amount smaller than the granularity, without crossing
|
||||||
|
* a granularity alignment boundary. Effectively a NOP.
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_shrink_negligible(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2;
|
||||||
|
ssize_t diff = -1;
|
||||||
|
int granularity = 1;
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, diff, granularity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grow by an amount smaller than the granularity, but crossing over
|
||||||
|
* a granularity alignment boundary.
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_grow_tiny(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2 - 2;
|
||||||
|
ssize_t diff = 1;
|
||||||
|
int granularity = 1;
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, diff, granularity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrink by an amount smaller than the granularity, but crossing over
|
||||||
|
* a granularity alignment boundary.
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_shrink_tiny(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2 - 1;
|
||||||
|
ssize_t diff = -1;
|
||||||
|
int granularity = 1;
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, diff, granularity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grow by an amount smaller than sizeof(long), and not crossing over
|
||||||
|
* a sizeof(long) alignment boundary.
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_grow_small(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2 + 1;
|
||||||
|
size_t diff = sizeof(long) / 2;
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, diff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrink by an amount smaller than sizeof(long), and not crossing over
|
||||||
|
* a sizeof(long) alignment boundary.
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_shrink_small(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2;
|
||||||
|
size_t diff = sizeof(long) / 2;
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, -diff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grow by an amount smaller than sizeof(long), while crossing over
|
||||||
|
* a sizeof(long) alignment boundary.
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_grow_medium(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2 - 1;
|
||||||
|
size_t diff = sizeof(long) / 2;
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, diff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrink by an amount smaller than sizeof(long), while crossing over
|
||||||
|
* a sizeof(long) alignment boundary.
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_shrink_medium(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2 + 1;
|
||||||
|
size_t diff = sizeof(long) / 2;
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, -diff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grow by an amount larger than sizeof(long).
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_grow_large(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2;
|
||||||
|
size_t diff = 8 * sizeof(long);
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, diff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrink by an amount larger than sizeof(long).
|
||||||
|
*/
|
||||||
|
static void test_hbitmap_truncate_shrink_large(TestHBitmapData *data,
|
||||||
|
const void *unused)
|
||||||
|
{
|
||||||
|
size_t size = L2;
|
||||||
|
size_t diff = 8 * sizeof(long);
|
||||||
|
|
||||||
|
hbitmap_test_truncate(data, size, -diff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static void hbitmap_test_add(const char *testpath,
|
static void hbitmap_test_add(const char *testpath,
|
||||||
void (*test_func)(TestHBitmapData *data, const void *user_data))
|
void (*test_func)(TestHBitmapData *data, const void *user_data))
|
||||||
{
|
{
|
||||||
|
@ -395,6 +628,28 @@ int main(int argc, char **argv)
|
||||||
hbitmap_test_add("/hbitmap/reset/empty", test_hbitmap_reset_empty);
|
hbitmap_test_add("/hbitmap/reset/empty", test_hbitmap_reset_empty);
|
||||||
hbitmap_test_add("/hbitmap/reset/general", test_hbitmap_reset);
|
hbitmap_test_add("/hbitmap/reset/general", test_hbitmap_reset);
|
||||||
hbitmap_test_add("/hbitmap/granularity", test_hbitmap_granularity);
|
hbitmap_test_add("/hbitmap/granularity", test_hbitmap_granularity);
|
||||||
|
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/nop", test_hbitmap_truncate_nop);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/grow/negligible",
|
||||||
|
test_hbitmap_truncate_grow_negligible);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/shrink/negligible",
|
||||||
|
test_hbitmap_truncate_shrink_negligible);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/grow/tiny",
|
||||||
|
test_hbitmap_truncate_grow_tiny);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/shrink/tiny",
|
||||||
|
test_hbitmap_truncate_shrink_tiny);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/grow/small",
|
||||||
|
test_hbitmap_truncate_grow_small);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/shrink/small",
|
||||||
|
test_hbitmap_truncate_shrink_small);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/grow/medium",
|
||||||
|
test_hbitmap_truncate_grow_medium);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/shrink/medium",
|
||||||
|
test_hbitmap_truncate_shrink_medium);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/grow/large",
|
||||||
|
test_hbitmap_truncate_grow_large);
|
||||||
|
hbitmap_test_add("/hbitmap/truncate/shrink/large",
|
||||||
|
test_hbitmap_truncate_shrink_large);
|
||||||
g_test_run();
|
g_test_run();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -170,12 +170,12 @@ restart:
|
||||||
if (elem->state != THREAD_DONE) {
|
if (elem->state != THREAD_DONE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (elem->state == THREAD_DONE) {
|
|
||||||
trace_thread_pool_complete(pool, elem, elem->common.opaque,
|
trace_thread_pool_complete(pool, elem, elem->common.opaque,
|
||||||
elem->ret);
|
elem->ret);
|
||||||
}
|
QLIST_REMOVE(elem, all);
|
||||||
if (elem->state == THREAD_DONE && elem->common.cb) {
|
|
||||||
QLIST_REMOVE(elem, all);
|
if (elem->common.cb) {
|
||||||
/* Read state before ret. */
|
/* Read state before ret. */
|
||||||
smp_rmb();
|
smp_rmb();
|
||||||
|
|
||||||
|
@ -188,8 +188,6 @@ restart:
|
||||||
qemu_aio_unref(elem);
|
qemu_aio_unref(elem);
|
||||||
goto restart;
|
goto restart;
|
||||||
} else {
|
} else {
|
||||||
/* remove the request */
|
|
||||||
QLIST_REMOVE(elem, all);
|
|
||||||
qemu_aio_unref(elem);
|
qemu_aio_unref(elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,9 @@ struct HBitmap {
|
||||||
* bitmap will still allocate HBITMAP_LEVELS arrays.
|
* bitmap will still allocate HBITMAP_LEVELS arrays.
|
||||||
*/
|
*/
|
||||||
unsigned long *levels[HBITMAP_LEVELS];
|
unsigned long *levels[HBITMAP_LEVELS];
|
||||||
|
|
||||||
|
/* The length of each levels[] array. */
|
||||||
|
uint64_t sizes[HBITMAP_LEVELS];
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Advance hbi to the next nonzero word and return it. hbi->pos
|
/* Advance hbi to the next nonzero word and return it. hbi->pos
|
||||||
|
@ -384,6 +387,7 @@ HBitmap *hbitmap_alloc(uint64_t size, int granularity)
|
||||||
hb->granularity = granularity;
|
hb->granularity = granularity;
|
||||||
for (i = HBITMAP_LEVELS; i-- > 0; ) {
|
for (i = HBITMAP_LEVELS; i-- > 0; ) {
|
||||||
size = MAX((size + BITS_PER_LONG - 1) >> BITS_PER_LEVEL, 1);
|
size = MAX((size + BITS_PER_LONG - 1) >> BITS_PER_LEVEL, 1);
|
||||||
|
hb->sizes[i] = size;
|
||||||
hb->levels[i] = g_new0(unsigned long, size);
|
hb->levels[i] = g_new0(unsigned long, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,3 +399,84 @@ HBitmap *hbitmap_alloc(uint64_t size, int granularity)
|
||||||
hb->levels[0][0] |= 1UL << (BITS_PER_LONG - 1);
|
hb->levels[0][0] |= 1UL << (BITS_PER_LONG - 1);
|
||||||
return hb;
|
return hb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void hbitmap_truncate(HBitmap *hb, uint64_t size)
|
||||||
|
{
|
||||||
|
bool shrink;
|
||||||
|
unsigned i;
|
||||||
|
uint64_t num_elements = size;
|
||||||
|
uint64_t old;
|
||||||
|
|
||||||
|
/* Size comes in as logical elements, adjust for granularity. */
|
||||||
|
size = (size + (1ULL << hb->granularity) - 1) >> hb->granularity;
|
||||||
|
assert(size <= ((uint64_t)1 << HBITMAP_LOG_MAX_SIZE));
|
||||||
|
shrink = size < hb->size;
|
||||||
|
|
||||||
|
/* bit sizes are identical; nothing to do. */
|
||||||
|
if (size == hb->size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we're losing bits, let's clear those bits before we invalidate all of
|
||||||
|
* our invariants. This helps keep the bitcount consistent, and will prevent
|
||||||
|
* us from carrying around garbage bits beyond the end of the map.
|
||||||
|
*/
|
||||||
|
if (shrink) {
|
||||||
|
/* Don't clear partial granularity groups;
|
||||||
|
* start at the first full one. */
|
||||||
|
uint64_t start = QEMU_ALIGN_UP(num_elements, 1 << hb->granularity);
|
||||||
|
uint64_t fix_count = (hb->size << hb->granularity) - start;
|
||||||
|
|
||||||
|
assert(fix_count);
|
||||||
|
hbitmap_reset(hb, start, fix_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
hb->size = size;
|
||||||
|
for (i = HBITMAP_LEVELS; i-- > 0; ) {
|
||||||
|
size = MAX(BITS_TO_LONGS(size), 1);
|
||||||
|
if (hb->sizes[i] == size) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
old = hb->sizes[i];
|
||||||
|
hb->sizes[i] = size;
|
||||||
|
hb->levels[i] = g_realloc(hb->levels[i], size * sizeof(unsigned long));
|
||||||
|
if (!shrink) {
|
||||||
|
memset(&hb->levels[i][old], 0x00,
|
||||||
|
(size - old) * sizeof(*hb->levels[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given HBitmaps A and B, let A := A (BITOR) B.
|
||||||
|
* Bitmap B will not be modified.
|
||||||
|
*
|
||||||
|
* @return true if the merge was successful,
|
||||||
|
* false if it was not attempted.
|
||||||
|
*/
|
||||||
|
bool hbitmap_merge(HBitmap *a, const HBitmap *b)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
uint64_t j;
|
||||||
|
|
||||||
|
if ((a->size != b->size) || (a->granularity != b->granularity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hbitmap_count(b) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This merge is O(size), as BITS_PER_LONG and HBITMAP_LEVELS are constant.
|
||||||
|
* It may be possible to improve running times for sparsely populated maps
|
||||||
|
* by using hbitmap_iter_next, but this is suboptimal for dense maps.
|
||||||
|
*/
|
||||||
|
for (i = HBITMAP_LEVELS - 1; i >= 0; i--) {
|
||||||
|
for (j = 0; j < a->sizes[i]; j++) {
|
||||||
|
a->levels[i][j] |= b->levels[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue