From patchwork Wed Aug 1 10:03:43 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Miroslav Rezanina X-Patchwork-Id: 174428 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id AB4C52C00CB for ; Wed, 1 Aug 2012 20:04:09 +1000 (EST) Received: from localhost ([::1]:42720 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SwVmh-0000jC-Hk for incoming@patchwork.ozlabs.org; Wed, 01 Aug 2012 06:04:07 -0400 Received: from eggs.gnu.org ([208.118.235.92]:34956) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SwVmT-0000iZ-5n for qemu-devel@nongnu.org; Wed, 01 Aug 2012 06:03:59 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SwVmM-0005As-CZ for qemu-devel@nongnu.org; Wed, 01 Aug 2012 06:03:53 -0400 Received: from mx3-phx2.redhat.com ([209.132.183.24]:38880) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SwVmL-0005Af-US for qemu-devel@nongnu.org; Wed, 01 Aug 2012 06:03:46 -0400 Received: from zmail17.collab.prod.int.phx2.redhat.com (zmail17.collab.prod.int.phx2.redhat.com [10.5.83.19]) by mx3-phx2.redhat.com (8.13.8/8.13.8) with ESMTP id q71A3iqt000429; Wed, 1 Aug 2012 06:03:44 -0400 Date: Wed, 1 Aug 2012 06:03:43 -0400 (EDT) From: Miroslav Rezanina To: qemu-devel@nongnu.org Message-ID: <490157254.6767622.1343815423950.JavaMail.root@redhat.com> In-Reply-To: <1736118218.6697488.1343814369561.JavaMail.root@redhat.com> MIME-Version: 1.0 X-Originating-IP: [10.34.26.132] X-Mailer: Zimbra 7.2.0_GA_2669 (ZimbraWebClient - GC21 (Linux)/7.2.0_GA_2669) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-Received-From: 209.132.183.24 Cc: Paolo Bonzini Subject: [Qemu-devel] [PATCH][RFC] Add compare subcommand for qemu-img X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This patch adds compare subcommand that compares two images. Compare has following criteria: - only data part is compared - unallocated sectors are not read - in case of different image size, exceeding part of bigger disk has to be zeroed/unallocated to compare rest - qemu-img returns: - 0 if images are identical - 1 if images differ - 2 on error Signed-off-by: Miroslav Rezanina Patch: diff --git a/block.c b/block.c index b38940b..919f8e9 100644 --- a/block.c +++ b/block.c @@ -2284,6 +2284,7 @@ int bdrv_has_zero_init(BlockDriverState *bs) typedef struct BdrvCoIsAllocatedData { BlockDriverState *bs; + BlockDriverState *base; int64_t sector_num; int nb_sectors; int *pnum; @@ -2414,6 +2415,44 @@ int coroutine_fn bdrv_co_is_allocated_above(BlockDriverState *top, return 0; } +/* Coroutine wrapper for bdrv_is_allocated_above() */ +static void coroutine_fn bdrv_is_allocated_above_co_entry(void *opaque) +{ + BdrvCoIsAllocatedData *data = opaque; + BlockDriverState *top = data->bs; + BlockDriverState *base = data->base; + + data->ret = bdrv_co_is_allocated_above(top, base, data->sector_num, + data->nb_sectors,data->pnum); + data->done = true; +} + +/* + * Synchronous wrapper around bdrv_co_is_allocated_above(). + * + * See bdrv_co_is_allocated_above() for details. + */ +int bdrv_is_allocated_above(BlockDriverState *top, BlockDriverState *base, + int64_t sector_num, int nb_sectors, int *pnum) +{ + Coroutine *co; + BdrvCoIsAllocatedData data = { + .bs = top, + .base = base, + .sector_num = sector_num, + .nb_sectors = nb_sectors, + .pnum = pnum, + .done = false, + }; + + co = qemu_coroutine_create(bdrv_is_allocated_above_co_entry); + qemu_coroutine_enter(co, &data); + while (!data.done) { + qemu_aio_wait(); + } + return data.ret; +} + BlockInfoList *qmp_query_block(Error **errp) { BlockInfoList *head = NULL, *cur_item = NULL; diff --git a/block.h b/block.h index c89590d..6f39da9 100644 --- a/block.h +++ b/block.h @@ -256,7 +256,8 @@ int bdrv_co_discard(BlockDriverState *bs, int64_t sector_num, int nb_sectors); int bdrv_has_zero_init(BlockDriverState *bs); int bdrv_is_allocated(BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum); - +int bdrv_is_allocated_above(BlockDriverState* top, BlockDriverState *base, + int64_t sector_num, int nb_sectors, int *pnum); void bdrv_set_on_error(BlockDriverState *bs, BlockErrorAction on_read_error, BlockErrorAction on_write_error); BlockErrorAction bdrv_get_on_error(BlockDriverState *bs, int is_read); diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 39419a0..5b34896 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -27,6 +27,12 @@ STEXI @item commit [-f @var{fmt}] [-t @var{cache}] @var{filename} ETEXI +DEF("compare", img_compare, + "compare [-f fmt] [-g fmt] [-p] filename1 filename2") +STEXI +@item compare [-f fmt] [-g fmt] [-p] filename1 filename2 +ETEXI + DEF("convert", img_convert, "convert [-c] [-p] [-f fmt] [-t cache] [-O output_fmt] [-o options] [-s snapshot_name] [-S sparse_size] filename [filename2 [...]] output_filename") STEXI diff --git a/qemu-img.c b/qemu-img.c index 80cfb9b..99a2f16 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -96,7 +96,9 @@ static void help(void) " '-a' applies a snapshot (revert disk to saved state)\n" " '-c' creates a snapshot\n" " '-d' deletes a snapshot\n" - " '-l' lists all snapshots in the given image\n"; + " '-l' lists all snapshots in the given image\n" + "Parameters to compare subcommand:\n" + " '-g' Second image format (in case it differs from first image)\n"; printf("%s\nSupported formats:", help_msg); bdrv_iterate_format(format_print, NULL); @@ -652,6 +654,223 @@ static int compare_sectors(const uint8_t *buf1, const uint8_t *buf2, int n, #define IO_BUF_SIZE (2 * 1024 * 1024) +/* + * Get number of sectors that can be stored in IO buffer. + */ + +static int64_t sectors_to_process(int64_t total, int64_t from) +{ + int64_t rv = total - from; + + if (rv > (IO_BUF_SIZE >> BDRV_SECTOR_BITS)) + return IO_BUF_SIZE >> BDRV_SECTOR_BITS; + + return rv; +} + +/* + * Compares two images. Exit codes: + * + * 0 - Images are identical + * 1 - Images differ + * 2 - Error occured + */ + +static int img_compare(int argc, char **argv) +{ + const char *fmt1 = NULL, *fmt2 = NULL, *filename1, *filename2; + BlockDriverState *bs1, *bs2; + int64_t total_sectors1, total_sectors2; + uint8_t *buf1 = NULL, *buf2 = NULL; + int pnum1, pnum2; + int allocated1,allocated2; + int flags = BDRV_O_FLAGS; + int progress=0,ret = 0; /* return value - 0 Ident, 1 Different, 2 Error */ + int64_t total_sectors; + int64_t sector_num = 0; + int64_t nb_sectors; + int c, rv, pnum; + uint64_t bs_sectors; + uint64_t progress_base; + + + for(;;) { + c = getopt(argc, argv, "pf:g:"); + if (c == -1) { + break; + } + switch(c) { + case 'f': + fmt1 = optarg; + if (fmt2 == NULL) + fmt2 = optarg; + break; + case 'g': + fmt2 = optarg; + break; + case 'p': + progress = 1; + break; + } + } + if (optind >= argc) { + help(); + goto out3; + } + filename1 = argv[optind++]; + filename2 = argv[optind++]; + + /* Initialize before goto out */ + qemu_progress_init(progress, 2.0); + + bs1 = bdrv_new_open(filename1, fmt1, flags); + if (!bs1) { + error_report("Can't open file %s", filename1); + ret = 2; + goto out3; + } + + bs2 = bdrv_new_open(filename2, fmt2, flags); + if (!bs2) { + error_report("Can't open file %s:",filename2); + ret = 2; + goto out2; + } + + qemu_progress_print(0, 100); + + buf1 = qemu_blockalign(bs1,IO_BUF_SIZE); + buf2 = qemu_blockalign(bs2,IO_BUF_SIZE); + bdrv_get_geometry(bs1,&bs_sectors); + total_sectors1 = bs_sectors; + bdrv_get_geometry(bs2,&bs_sectors); + total_sectors2 = bs_sectors; + total_sectors = total_sectors1; + progress_base = total_sectors; + + if (total_sectors1 != total_sectors2) { + BlockDriverState *bsover; + int64_t over_sectors, over_num; + const char *filename_over; + + if (total_sectors1 > total_sectors2) { + total_sectors = total_sectors2; + over_sectors = total_sectors1; + over_num = total_sectors2; + bsover = bs1; + filename_over = filename1; + } else { + total_sectors = total_sectors1; + over_sectors = total_sectors2; + over_num = total_sectors1; + bsover = bs1; + filename_over = filename2; + } + + progress_base = over_sectors; + + for (;;) { + if ((nb_sectors = sectors_to_process(over_sectors,over_num)) <= 0) + break; + + rv = bdrv_is_allocated(bsover,over_num,nb_sectors,&pnum); + nb_sectors = pnum; + if (rv) { + rv = bdrv_read(bsover, over_num, buf1, nb_sectors); + if (rv < 0) { + error_report("error while reading sector %" PRId64 " of %s:" + " %s",over_num, filename_over, strerror(-rv)); + ret = 2; + goto out; + } + rv = is_allocated_sectors(buf1, nb_sectors, &pnum); + if (rv || pnum != nb_sectors) { + ret = 1; + printf("Images are different - image size mismatch!\n"); + goto out; + } + } + over_num += nb_sectors; + qemu_progress_print(((float) nb_sectors / progress_base)*100, 100); + } + } + + for (;;) { + if ((nb_sectors = sectors_to_process(total_sectors, sector_num)) <= 0) + break; + allocated1 = bdrv_is_allocated_above(bs1, NULL, sector_num, nb_sectors, + &pnum1); + allocated2 = bdrv_is_allocated_above(bs2, NULL, sector_num, nb_sectors, + &pnum2); + if (pnum1 < pnum2) { + nb_sectors = pnum1; + } else { + nb_sectors = pnum2; + } + + if (allocated1 == allocated2) { + if (allocated1) { + rv = bdrv_read(bs1, sector_num, buf1, nb_sectors); + if (rv < 0) { + ret = 2; + error_report("error while reading sector %" PRId64 " of %s:" + " %s", sector_num, filename1, strerror(-rv)); + goto out; + } + rv = bdrv_read(bs2,sector_num,buf2, nb_sectors); + if (rv < 0) { + ret = 2; + error_report("error while reading sector %" PRId64 " of %s:" + " %s", sector_num, filename2, strerror(-rv)); + goto out; + } + rv = compare_sectors(buf1,buf2,nb_sectors,&pnum); + if (rv || pnum != nb_sectors) { + ret = 1; + printf("Images are different - content mismatch!\n"); + goto out; + } + } + } else { + BlockDriverState *bstmp; + const char* filenametmp; + if (allocated1) { + bstmp = bs1; + filenametmp = filename1; + } else { + bstmp = bs2; + filenametmp = filename2; + } + rv = bdrv_read(bstmp, sector_num, buf1, nb_sectors); + if (rv < 0) { + ret = 2; + error_report("error while reading sector %" PRId64 " of %s: %s", + sector_num, filenametmp, strerror(-rv)); + goto out; + } + rv = is_allocated_sectors(buf1, nb_sectors, &pnum); + if (rv || pnum != nb_sectors) { + ret = 1; + printf("Images are different - content mismatch!\n"); + goto out; + } + } + sector_num += nb_sectors; + qemu_progress_print(((float) nb_sectors / progress_base)*100, 100); + } + printf("Images are identical.\n"); + +out: + bdrv_delete(bs2); + qemu_vfree(buf1); + qemu_vfree(buf2); +out2: + bdrv_delete(bs1); +out3: + qemu_progress_end(); + return ret; +} + static int img_convert(int argc, char **argv) { int c, ret = 0, n, n1, bs_n, bs_i, compress, cluster_size, cluster_sectors;