mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-23 11:39:53 +00:00
qemu-img: Add compare subcommand
This patch adds new qemu-img subcommand that compares content of two disk images. Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
parent
f382d43a91
commit
d14ed18c8d
@ -27,6 +27,12 @@ STEXI
|
||||
@item commit [-q] [-f @var{fmt}] [-t @var{cache}] @var{filename}
|
||||
ETEXI
|
||||
|
||||
DEF("compare", img_compare,
|
||||
"compare [-f fmt] [-F fmt] [-p] [-q] [-s] filename1 filename2")
|
||||
STEXI
|
||||
@item compare [-f @var{fmt}] [-F @var{fmt}] [-p] [-q] [-s] @var{filename1} @var{filename2}
|
||||
ETEXI
|
||||
|
||||
DEF("convert", img_convert,
|
||||
"convert [-c] [-p] [-q] [-f fmt] [-t cache] [-O output_fmt] [-o options] [-s snapshot_name] [-S sparse_size] filename [filename2 [...]] output_filename")
|
||||
STEXI
|
||||
|
290
qemu-img.c
290
qemu-img.c
@ -113,7 +113,12 @@ 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"
|
||||
"\n"
|
||||
"Parameters to compare subcommand:\n"
|
||||
" '-f' first image format\n"
|
||||
" '-F' second image format\n"
|
||||
" '-s' run in Strict mode - fail on different image size or sector allocation\n";
|
||||
|
||||
printf("%s\nSupported formats:", help_msg);
|
||||
bdrv_iterate_format(format_print, NULL);
|
||||
@ -820,6 +825,289 @@ static int compare_sectors(const uint8_t *buf1, const uint8_t *buf2, int n,
|
||||
|
||||
#define IO_BUF_SIZE (2 * 1024 * 1024)
|
||||
|
||||
static int64_t sectors_to_bytes(int64_t sectors)
|
||||
{
|
||||
return sectors << BDRV_SECTOR_BITS;
|
||||
}
|
||||
|
||||
static int64_t sectors_to_process(int64_t total, int64_t from)
|
||||
{
|
||||
return MIN(total - from, IO_BUF_SIZE >> BDRV_SECTOR_BITS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if passed sectors are empty (not allocated or contain only 0 bytes)
|
||||
*
|
||||
* Returns 0 in case sectors are filled with 0, 1 if sectors contain non-zero
|
||||
* data and negative value on error.
|
||||
*
|
||||
* @param bs: Driver used for accessing file
|
||||
* @param sect_num: Number of first sector to check
|
||||
* @param sect_count: Number of sectors to check
|
||||
* @param filename: Name of disk file we are checking (logging purpose)
|
||||
* @param buffer: Allocated buffer for storing read data
|
||||
* @param quiet: Flag for quiet mode
|
||||
*/
|
||||
static int check_empty_sectors(BlockDriverState *bs, int64_t sect_num,
|
||||
int sect_count, const char *filename,
|
||||
uint8_t *buffer, bool quiet)
|
||||
{
|
||||
int pnum, ret = 0;
|
||||
ret = bdrv_read(bs, sect_num, buffer, sect_count);
|
||||
if (ret < 0) {
|
||||
error_report("Error while reading offset %" PRId64 " of %s: %s",
|
||||
sectors_to_bytes(sect_num), filename, strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
ret = is_allocated_sectors(buffer, sect_count, &pnum);
|
||||
if (ret || pnum != sect_count) {
|
||||
qprintf(quiet, "Content mismatch at offset %" PRId64 "!\n",
|
||||
sectors_to_bytes(ret ? sect_num : sect_num + pnum));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compares two images. Exit codes:
|
||||
*
|
||||
* 0 - Images are identical
|
||||
* 1 - Images differ
|
||||
* >1 - Error occurred
|
||||
*/
|
||||
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 ret = 0; /* return value - 0 Ident, 1 Different, >1 Error */
|
||||
bool progress = false, quiet = false, strict = false;
|
||||
int64_t total_sectors;
|
||||
int64_t sector_num = 0;
|
||||
int64_t nb_sectors;
|
||||
int c, pnum;
|
||||
uint64_t bs_sectors;
|
||||
uint64_t progress_base;
|
||||
|
||||
for (;;) {
|
||||
c = getopt(argc, argv, "hpf:F:sq");
|
||||
if (c == -1) {
|
||||
break;
|
||||
}
|
||||
switch (c) {
|
||||
case '?':
|
||||
case 'h':
|
||||
help();
|
||||
break;
|
||||
case 'f':
|
||||
fmt1 = optarg;
|
||||
break;
|
||||
case 'F':
|
||||
fmt2 = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
progress = true;
|
||||
break;
|
||||
case 'q':
|
||||
quiet = true;
|
||||
break;
|
||||
case 's':
|
||||
strict = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Progress is not shown in Quiet mode */
|
||||
if (quiet) {
|
||||
progress = false;
|
||||
}
|
||||
|
||||
|
||||
if (optind > argc - 2) {
|
||||
help();
|
||||
}
|
||||
filename1 = argv[optind++];
|
||||
filename2 = argv[optind++];
|
||||
|
||||
/* Initialize before goto out */
|
||||
qemu_progress_init(progress, 2.0);
|
||||
|
||||
bs1 = bdrv_new_open(filename1, fmt1, BDRV_O_FLAGS, true, quiet);
|
||||
if (!bs1) {
|
||||
error_report("Can't open file %s", filename1);
|
||||
ret = 2;
|
||||
goto out3;
|
||||
}
|
||||
|
||||
bs2 = bdrv_new_open(filename2, fmt2, BDRV_O_FLAGS, true, quiet);
|
||||
if (!bs2) {
|
||||
error_report("Can't open file %s", filename2);
|
||||
ret = 2;
|
||||
goto out2;
|
||||
}
|
||||
|
||||
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 = MIN(total_sectors1, total_sectors2);
|
||||
progress_base = MAX(total_sectors1, total_sectors2);
|
||||
|
||||
qemu_progress_print(0, 100);
|
||||
|
||||
if (strict && total_sectors1 != total_sectors2) {
|
||||
ret = 1;
|
||||
qprintf(quiet, "Strict mode: Image size mismatch!\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
nb_sectors = sectors_to_process(total_sectors, sector_num);
|
||||
if (nb_sectors <= 0) {
|
||||
break;
|
||||
}
|
||||
allocated1 = bdrv_is_allocated_above(bs1, NULL, sector_num, nb_sectors,
|
||||
&pnum1);
|
||||
if (allocated1 < 0) {
|
||||
ret = 3;
|
||||
error_report("Sector allocation test failed for %s", filename1);
|
||||
goto out;
|
||||
}
|
||||
|
||||
allocated2 = bdrv_is_allocated_above(bs2, NULL, sector_num, nb_sectors,
|
||||
&pnum2);
|
||||
if (allocated2 < 0) {
|
||||
ret = 3;
|
||||
error_report("Sector allocation test failed for %s", filename2);
|
||||
goto out;
|
||||
}
|
||||
nb_sectors = MIN(pnum1, pnum2);
|
||||
|
||||
if (allocated1 == allocated2) {
|
||||
if (allocated1) {
|
||||
ret = bdrv_read(bs1, sector_num, buf1, nb_sectors);
|
||||
if (ret < 0) {
|
||||
error_report("Error while reading offset %" PRId64 " of %s:"
|
||||
" %s", sectors_to_bytes(sector_num), filename1,
|
||||
strerror(-ret));
|
||||
ret = 4;
|
||||
goto out;
|
||||
}
|
||||
ret = bdrv_read(bs2, sector_num, buf2, nb_sectors);
|
||||
if (ret < 0) {
|
||||
error_report("Error while reading offset %" PRId64
|
||||
" of %s: %s", sectors_to_bytes(sector_num),
|
||||
filename2, strerror(-ret));
|
||||
ret = 4;
|
||||
goto out;
|
||||
}
|
||||
ret = compare_sectors(buf1, buf2, nb_sectors, &pnum);
|
||||
if (ret || pnum != nb_sectors) {
|
||||
ret = 1;
|
||||
qprintf(quiet, "Content mismatch at offset %" PRId64 "!\n",
|
||||
sectors_to_bytes(
|
||||
ret ? sector_num : sector_num + pnum));
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (strict) {
|
||||
ret = 1;
|
||||
qprintf(quiet, "Strict mode: Offset %" PRId64
|
||||
" allocation mismatch!\n",
|
||||
sectors_to_bytes(sector_num));
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (allocated1) {
|
||||
ret = check_empty_sectors(bs1, sector_num, nb_sectors,
|
||||
filename1, buf1, quiet);
|
||||
} else {
|
||||
ret = check_empty_sectors(bs2, sector_num, nb_sectors,
|
||||
filename2, buf1, quiet);
|
||||
}
|
||||
if (ret) {
|
||||
if (ret < 0) {
|
||||
ret = 4;
|
||||
error_report("Error while reading offset %" PRId64 ": %s",
|
||||
sectors_to_bytes(sector_num), strerror(-ret));
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
sector_num += nb_sectors;
|
||||
qemu_progress_print(((float) nb_sectors / progress_base)*100, 100);
|
||||
}
|
||||
|
||||
if (total_sectors1 != total_sectors2) {
|
||||
BlockDriverState *bs_over;
|
||||
int64_t total_sectors_over;
|
||||
const char *filename_over;
|
||||
|
||||
qprintf(quiet, "Warning: Image size mismatch!\n");
|
||||
if (total_sectors1 > total_sectors2) {
|
||||
total_sectors_over = total_sectors1;
|
||||
bs_over = bs1;
|
||||
filename_over = filename1;
|
||||
} else {
|
||||
total_sectors_over = total_sectors2;
|
||||
bs_over = bs2;
|
||||
filename_over = filename2;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
nb_sectors = sectors_to_process(total_sectors_over, sector_num);
|
||||
if (nb_sectors <= 0) {
|
||||
break;
|
||||
}
|
||||
ret = bdrv_is_allocated_above(bs_over, NULL, sector_num,
|
||||
nb_sectors, &pnum);
|
||||
if (ret < 0) {
|
||||
ret = 3;
|
||||
error_report("Sector allocation test failed for %s",
|
||||
filename_over);
|
||||
goto out;
|
||||
|
||||
}
|
||||
nb_sectors = pnum;
|
||||
if (ret) {
|
||||
ret = check_empty_sectors(bs_over, sector_num, nb_sectors,
|
||||
filename_over, buf1, quiet);
|
||||
if (ret) {
|
||||
if (ret < 0) {
|
||||
ret = 4;
|
||||
error_report("Error while reading offset %" PRId64
|
||||
" of %s: %s", sectors_to_bytes(sector_num),
|
||||
filename_over, strerror(-ret));
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
sector_num += nb_sectors;
|
||||
qemu_progress_print(((float) nb_sectors / progress_base)*100, 100);
|
||||
}
|
||||
}
|
||||
|
||||
qprintf(quiet, "Images are identical.\n");
|
||||
ret = 0;
|
||||
|
||||
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;
|
||||
|
@ -84,6 +84,18 @@ deletes a snapshot
|
||||
lists all snapshots in the given image
|
||||
@end table
|
||||
|
||||
Parameters to compare subcommand:
|
||||
|
||||
@table @option
|
||||
|
||||
@item -f
|
||||
First image format
|
||||
@item -F
|
||||
Second image format
|
||||
@item -s
|
||||
Strict mode - fail on on different image size or sector allocation
|
||||
@end table
|
||||
|
||||
Command description:
|
||||
|
||||
@table @option
|
||||
@ -118,6 +130,47 @@ it doesn't need to be specified separately in this case.
|
||||
|
||||
Commit the changes recorded in @var{filename} in its base image.
|
||||
|
||||
@item compare [-f @var{fmt}] [-F @var{fmt}] [-p] [-s] [-q] @var{filename1} @var{filename2}
|
||||
|
||||
Check if two images have the same content. You can compare images with
|
||||
different format or settings.
|
||||
|
||||
The format is probed unless you specify it by @var{-f} (used for
|
||||
@var{filename1}) and/or @var{-F} (used for @var{filename2}) option.
|
||||
|
||||
By default, images with different size are considered identical if the larger
|
||||
image contains only unallocated and/or zeroed sectors in the area after the end
|
||||
of the other image. In addition, if any sector is not allocated in one image
|
||||
and contains only zero bytes in the second one, it is evaluated as equal. You
|
||||
can use Strict mode by specifying the @var{-s} option. When compare runs in
|
||||
Strict mode, it fails in case image size differs or a sector is allocated in
|
||||
one image and is not allocated in the second one.
|
||||
|
||||
By default, compare prints out a result message. This message displays
|
||||
information that both images are same or the position of the first different
|
||||
byte. In addition, result message can report different image size in case
|
||||
Strict mode is used.
|
||||
|
||||
Compare exits with @code{0} in case the images are equal and with @code{1}
|
||||
in case the images differ. Other exit codes mean an error occurred during
|
||||
execution and standard error output should contain an error message.
|
||||
The following table sumarizes all exit codes of the compare subcommand:
|
||||
|
||||
@table @option
|
||||
|
||||
@item 0
|
||||
Images are identical
|
||||
@item 1
|
||||
Images differ
|
||||
@item 2
|
||||
Error on opening an image
|
||||
@item 3
|
||||
Error on checking a sector allocation
|
||||
@item 4
|
||||
Error on reading data
|
||||
|
||||
@end table
|
||||
|
||||
@item convert [-c] [-p] [-f @var{fmt}] [-t @var{cache}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_name}] [-S @var{sparse_size}] @var{filename} [@var{filename2} [...]] @var{output_filename}
|
||||
|
||||
Convert the disk image @var{filename} or a snapshot @var{snapshot_name} to disk image @var{output_filename}
|
||||
|
Loading…
Reference in New Issue
Block a user