From c6bb9ad198c2caa9c7c8ba360a07630b5c10e4a8 Mon Sep 17 00:00:00 2001 From: Federico Simoncelli Date: Mon, 28 Jan 2013 06:59:46 -0500 Subject: [PATCH 01/22] qemu-img: find the image end offset during check This patch adds the support for reporting the image end offset (in bytes). This is particularly useful after a conversion (or a rebase) where the destination is a block device in order to find the first unused byte at the end of the image. Signed-off-by: Federico Simoncelli Signed-off-by: Kevin Wolf --- block/qcow2-refcount.c | 10 ++++++++-- include/block/block.h | 1 + qemu-img.c | 4 ++++ tests/qemu-iotests/026 | 6 +++--- tests/qemu-iotests/036 | 3 ++- tests/qemu-iotests/039 | 2 +- tests/qemu-iotests/044.out | 1 + tests/qemu-iotests/common.rc | 7 ++++--- 8 files changed, 24 insertions(+), 10 deletions(-) diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index bc1784c30e..d36cb4dd2f 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -1112,7 +1112,7 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res, BdrvCheckMode fix) { BDRVQcowState *s = bs->opaque; - int64_t size, i; + int64_t size, i, highest_cluster; int nb_clusters, refcount1, refcount2; QCowSnapshot *sn; uint16_t *refcount_table; @@ -1183,7 +1183,7 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res, } /* compare ref counts */ - for(i = 0; i < nb_clusters; i++) { + for (i = 0, highest_cluster = 0; i < nb_clusters; i++) { refcount1 = get_refcount(bs, i); if (refcount1 < 0) { fprintf(stderr, "Can't get refcount for cluster %" PRId64 ": %s\n", @@ -1193,6 +1193,11 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res, } refcount2 = refcount_table[i]; + + if (refcount1 > 0 || refcount2 > 0) { + highest_cluster = i; + } + if (refcount1 != refcount2) { /* Check if we're allowed to fix the mismatch */ @@ -1227,6 +1232,7 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res, } } + res->image_end_offset = (highest_cluster + 1) * s->cluster_size; ret = 0; fail: diff --git a/include/block/block.h b/include/block/block.h index 5c3b911c1b..ce61883fc9 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -213,6 +213,7 @@ typedef struct BdrvCheckResult { int check_errors; int corruptions_fixed; int leaks_fixed; + int64_t image_end_offset; BlockFragInfo bfi; } BdrvCheckResult; diff --git a/qemu-img.c b/qemu-img.c index 85d3740b9c..e80c1c55fd 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -475,6 +475,10 @@ static int img_check(int argc, char **argv) result.bfi.fragmented_clusters * 100.0 / result.bfi.allocated_clusters); } + if (result.image_end_offset > 0) { + printf("Image end offset: %" PRId64 "\n", result.image_end_offset); + } + bdrv_delete(bs); if (ret < 0 || result.check_errors) { diff --git a/tests/qemu-iotests/026 b/tests/qemu-iotests/026 index 1602ccd2a5..107a3ff2f6 100755 --- a/tests/qemu-iotests/026 +++ b/tests/qemu-iotests/026 @@ -102,7 +102,7 @@ if [ "$event" == "l2_load" ]; then $QEMU_IO -c "read $vmstate 0 128k " $BLKDBG_TEST_IMG | _filter_qemu_io fi -$QEMU_IMG check $TEST_IMG 2>&1 | grep -v "refcount=1 reference=0" +_check_test_img 2>&1 | grep -v "refcount=1 reference=0" done done @@ -147,7 +147,7 @@ echo echo "Event: $event; errno: $errno; imm: $imm; once: $once; write $vmstate" $QEMU_IO -c "write $vmstate 0 64M" $BLKDBG_TEST_IMG | _filter_qemu_io -$QEMU_IMG check $TEST_IMG 2>&1 | grep -v "refcount=1 reference=0" +_check_test_img 2>&1 | grep -v "refcount=1 reference=0" done done @@ -186,7 +186,7 @@ echo echo "Event: $event; errno: $errno; imm: $imm; once: $once" $QEMU_IO -c "write -b 0 64k" $BLKDBG_TEST_IMG | _filter_qemu_io -$QEMU_IMG check $TEST_IMG 2>&1 | grep -v "refcount=1 reference=0" +_check_test_img 2>&1 | grep -v "refcount=1 reference=0" done done diff --git a/tests/qemu-iotests/036 b/tests/qemu-iotests/036 index 329533e9ea..4dbfc5724c 100755 --- a/tests/qemu-iotests/036 +++ b/tests/qemu-iotests/036 @@ -59,7 +59,8 @@ _make_test_img 64M echo echo === Repair image === echo -$QEMU_IMG check -r all $TEST_IMG +_check_test_img -r all + ./qcow2.py $TEST_IMG dump-header # success, all done diff --git a/tests/qemu-iotests/039 b/tests/qemu-iotests/039 index c5ae806ecb..ae3517575c 100755 --- a/tests/qemu-iotests/039 +++ b/tests/qemu-iotests/039 @@ -86,7 +86,7 @@ $QEMU_IO -r -c "read -P 0x5a 0 512" $TEST_IMG | _filter_qemu_io echo echo "== Repairing the image file must succeed ==" -$QEMU_IMG check -r all $TEST_IMG +_check_test_img -r all # The dirty bit must not be set ./qcow2.py $TEST_IMG dump-header | grep incompatible_features diff --git a/tests/qemu-iotests/044.out b/tests/qemu-iotests/044.out index 7a4007137d..9c4867329f 100644 --- a/tests/qemu-iotests/044.out +++ b/tests/qemu-iotests/044.out @@ -1,4 +1,5 @@ No errors were found on the image. +Image end offset: 4296447488 . ---------------------------------------------------------------------- Ran 1 tests diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc index aef5f52b4f..5ba960b09f 100644 --- a/tests/qemu-iotests/common.rc +++ b/tests/qemu-iotests/common.rc @@ -161,9 +161,10 @@ _cleanup_test_img() _check_test_img() { - $QEMU_IMG check -f $IMGFMT $TEST_IMG 2>&1 | \ - grep -v "fragmented$" | \ - sed -e 's/qemu-img\: This image format does not support checks/No errors were found on the image./' + $QEMU_IMG check "$@" -f $IMGFMT $TEST_IMG 2>&1 | \ + sed -e "/fragmented$/d" \ + -e 's/qemu-img: This image format does not support checks/No errors were found on the image./' \ + -e '/Image end offset: [0-9]\+/d' } _img_info() From 8599ea4c42c098d2657ed632ad569f7a665706a4 Mon Sep 17 00:00:00 2001 From: Federico Simoncelli Date: Mon, 28 Jan 2013 06:59:47 -0500 Subject: [PATCH 02/22] qemu-img: add json output option to the check command This option --output=[human|json] makes qemu-img check output a human or JSON representation at the choice of the user. Signed-off-by: Federico Simoncelli Reviewed-by: Eric Blake Signed-off-by: Kevin Wolf --- qapi-schema.json | 46 +++++++++ qemu-img-cmds.hx | 4 +- qemu-img.c | 244 +++++++++++++++++++++++++++++++++-------------- qemu-img.texi | 5 +- 4 files changed, 226 insertions(+), 73 deletions(-) diff --git a/qapi-schema.json b/qapi-schema.json index cd7ea25e4c..c20725c7f7 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -244,6 +244,52 @@ '*backing-filename': 'str', '*full-backing-filename': 'str', '*backing-filename-format': 'str', '*snapshots': ['SnapshotInfo'] } } +## +# @ImageCheck: +# +# Information about a QEMU image file check +# +# @filename: name of the image file checked +# +# @format: format of the image file checked +# +# @check-errors: number of unexpected errors occurred during check +# +# @image-end-offset: #optional offset (in bytes) where the image ends, this +# field is present if the driver for the image format +# supports it +# +# @corruptions: #optional number of corruptions found during the check if any +# +# @leaks: #optional number of leaks found during the check if any +# +# @corruptions-fixed: #optional number of corruptions fixed during the check +# if any +# +# @leaks-fixed: #optional number of leaks fixed during the check if any +# +# @total-clusters: #optional total number of clusters, this field is present +# if the driver for the image format supports it +# +# @allocated-clusters: #optional total number of allocated clusters, this +# field is present if the driver for the image format +# supports it +# +# @fragmented-clusters: #optional total number of fragmented clusters, this +# field is present if the driver for the image format +# supports it +# +# Since: 1.4 +# +## + +{ 'type': 'ImageCheck', + 'data': {'filename': 'str', 'format': 'str', 'check-errors': 'int', + '*image-end-offset': 'int', '*corruptions': 'int', '*leaks': 'int', + '*corruptions-fixed': 'int', '*leaks-fixed': 'int', + '*total-clusters': 'int', '*allocated-clusters': 'int', + '*fragmented-clusters': 'int' } } + ## # @StatusInfo: # diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index a18136302d..259fc142ed 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -10,9 +10,9 @@ STEXI ETEXI DEF("check", img_check, - "check [-f fmt] [-r [leaks | all]] filename") + "check [-f fmt] [--output=ofmt] [-r [leaks | all]] filename") STEXI -@item check [-f @var{fmt}] [-r [leaks | all]] @var{filename} +@item check [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] @var{filename} ETEXI DEF("create", img_create, diff --git a/qemu-img.c b/qemu-img.c index e80c1c55fd..34249fee12 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -42,6 +42,16 @@ typedef struct img_cmd_t { int (*handler)(int argc, char **argv); } img_cmd_t; +enum { + OPTION_OUTPUT = 256, + OPTION_BACKING_CHAIN = 257, +}; + +typedef enum OutputFormat { + OFORMAT_JSON, + OFORMAT_HUMAN, +} OutputFormat; + /* Default to cache=writeback as data integrity is not important for qemu-tcg. */ #define BDRV_O_FLAGS BDRV_O_CACHE_WB #define BDRV_DEFAULT_CACHE "writeback" @@ -375,6 +385,96 @@ static int img_create(int argc, char **argv) return 0; } +static void dump_json_image_check(ImageCheck *check) +{ + Error *errp = NULL; + QString *str; + QmpOutputVisitor *ov = qmp_output_visitor_new(); + QObject *obj; + visit_type_ImageCheck(qmp_output_get_visitor(ov), + &check, NULL, &errp); + obj = qmp_output_get_qobject(ov); + str = qobject_to_json_pretty(obj); + assert(str != NULL); + printf("%s\n", qstring_get_str(str)); + qobject_decref(obj); + qmp_output_visitor_cleanup(ov); + QDECREF(str); +} + +static void dump_human_image_check(ImageCheck *check) +{ + if (!(check->corruptions || check->leaks || check->check_errors)) { + printf("No errors were found on the image.\n"); + } else { + if (check->corruptions) { + printf("\n%" PRId64 " errors were found on the image.\n" + "Data may be corrupted, or further writes to the image " + "may corrupt it.\n", + check->corruptions); + } + + if (check->leaks) { + printf("\n%" PRId64 " leaked clusters were found on the image.\n" + "This means waste of disk space, but no harm to data.\n", + check->leaks); + } + + if (check->check_errors) { + printf("\n%" PRId64 " internal errors have occurred during the check.\n", + check->check_errors); + } + } + + if (check->total_clusters != 0 && check->allocated_clusters != 0) { + printf("%" PRId64 "/%" PRId64 "= %0.2f%% allocated, %0.2f%% fragmented\n", + check->allocated_clusters, check->total_clusters, + check->allocated_clusters * 100.0 / check->total_clusters, + check->fragmented_clusters * 100.0 / check->allocated_clusters); + } + + if (check->image_end_offset) { + printf("Image end offset: %" PRId64 "\n", check->image_end_offset); + } +} + +static int collect_image_check(BlockDriverState *bs, + ImageCheck *check, + const char *filename, + const char *fmt, + int fix) +{ + int ret; + BdrvCheckResult result; + + ret = bdrv_check(bs, &result, fix); + if (ret < 0) { + return ret; + } + + check->filename = g_strdup(filename); + check->format = g_strdup(bdrv_get_format_name(bs)); + check->check_errors = result.check_errors; + check->corruptions = result.corruptions; + check->has_corruptions = result.corruptions != 0; + check->leaks = result.leaks; + check->has_leaks = result.leaks != 0; + check->corruptions_fixed = result.corruptions_fixed; + check->has_corruptions_fixed = result.corruptions != 0; + check->leaks_fixed = result.leaks_fixed; + check->has_leaks_fixed = result.leaks != 0; + check->image_end_offset = result.image_end_offset; + check->has_image_end_offset = result.image_end_offset != 0; + check->total_clusters = result.bfi.total_clusters; + check->has_total_clusters = result.bfi.total_clusters != 0; + check->allocated_clusters = result.bfi.allocated_clusters; + check->has_allocated_clusters = result.bfi.allocated_clusters != 0; + check->fragmented_clusters = result.bfi.fragmented_clusters; + check->has_fragmented_clusters = result.bfi.fragmented_clusters != 0; + + return 0; +} + /* * Checks an image for consistency. Exit codes: * @@ -386,15 +486,26 @@ static int img_create(int argc, char **argv) static int img_check(int argc, char **argv) { int c, ret; - const char *filename, *fmt; + OutputFormat output_format = OFORMAT_HUMAN; + const char *filename, *fmt, *output; BlockDriverState *bs; - BdrvCheckResult result; int fix = 0; int flags = BDRV_O_FLAGS | BDRV_O_CHECK; + ImageCheck *check; fmt = NULL; + output = NULL; for(;;) { - c = getopt(argc, argv, "f:hr:"); + int option_index = 0; + static const struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"format", required_argument, 0, 'f'}, + {"repair", no_argument, 0, 'r'}, + {"output", required_argument, 0, OPTION_OUTPUT}, + {0, 0, 0, 0} + }; + c = getopt_long(argc, argv, "f:hr:", + long_options, &option_index); if (c == -1) { break; } @@ -417,6 +528,9 @@ static int img_check(int argc, char **argv) help(); } break; + case OPTION_OUTPUT: + output = optarg; + break; } } if (optind >= argc) { @@ -424,77 +538,79 @@ static int img_check(int argc, char **argv) } filename = argv[optind++]; + if (output && !strcmp(output, "json")) { + output_format = OFORMAT_JSON; + } else if (output && !strcmp(output, "human")) { + output_format = OFORMAT_HUMAN; + } else if (output) { + error_report("--output must be used with human or json as argument."); + return 1; + } + bs = bdrv_new_open(filename, fmt, flags, true); if (!bs) { return 1; } - ret = bdrv_check(bs, &result, fix); + + check = g_new0(ImageCheck, 1); + ret = collect_image_check(bs, check, filename, fmt, fix); if (ret == -ENOTSUP) { - error_report("This image format does not support checks"); - bdrv_delete(bs); - return 1; + if (output_format == OFORMAT_HUMAN) { + error_report("This image format does not support checks"); + } + ret = 1; + goto fail; } - if (result.corruptions_fixed || result.leaks_fixed) { - printf("The following inconsistencies were found and repaired:\n\n" - " %d leaked clusters\n" - " %d corruptions\n\n" - "Double checking the fixed image now...\n", - result.leaks_fixed, - result.corruptions_fixed); - ret = bdrv_check(bs, &result, 0); + if (check->corruptions_fixed || check->leaks_fixed) { + int corruptions_fixed, leaks_fixed; + + leaks_fixed = check->leaks_fixed; + corruptions_fixed = check->corruptions_fixed; + + if (output_format == OFORMAT_HUMAN) { + printf("The following inconsistencies were found and repaired:\n\n" + " %" PRId64 " leaked clusters\n" + " %" PRId64 " corruptions\n\n" + "Double checking the fixed image now...\n", + check->leaks_fixed, + check->corruptions_fixed); + } + + ret = collect_image_check(bs, check, filename, fmt, 0); + + check->leaks_fixed = leaks_fixed; + check->corruptions_fixed = corruptions_fixed; } - if (!(result.corruptions || result.leaks || result.check_errors)) { - printf("No errors were found on the image.\n"); + switch (output_format) { + case OFORMAT_HUMAN: + dump_human_image_check(check); + break; + case OFORMAT_JSON: + dump_json_image_check(check); + break; + } + + if (ret || check->check_errors) { + ret = 1; + goto fail; + } + + if (check->corruptions) { + ret = 2; + } else if (check->leaks) { + ret = 3; } else { - if (result.corruptions) { - printf("\n%d errors were found on the image.\n" - "Data may be corrupted, or further writes to the image " - "may corrupt it.\n", - result.corruptions); - } - - if (result.leaks) { - printf("\n%d leaked clusters were found on the image.\n" - "This means waste of disk space, but no harm to data.\n", - result.leaks); - } - - if (result.check_errors) { - printf("\n%d internal errors have occurred during the check.\n", - result.check_errors); - } - } - - if (result.bfi.total_clusters != 0 && result.bfi.allocated_clusters != 0) { - printf("%" PRId64 "/%" PRId64 "= %0.2f%% allocated, %0.2f%% fragmented\n", - result.bfi.allocated_clusters, result.bfi.total_clusters, - result.bfi.allocated_clusters * 100.0 / result.bfi.total_clusters, - result.bfi.fragmented_clusters * 100.0 / result.bfi.allocated_clusters); - } - - if (result.image_end_offset > 0) { - printf("Image end offset: %" PRId64 "\n", result.image_end_offset); + ret = 0; } +fail: + qapi_free_ImageCheck(check); bdrv_delete(bs); - if (ret < 0 || result.check_errors) { - printf("\nAn error has occurred during the check: %s\n" - "The check is not complete and may have missed error.\n", - strerror(-ret)); - return 1; - } - - if (result.corruptions) { - return 2; - } else if (result.leaks) { - return 3; - } else { - return 0; - } + return ret; } static int img_commit(int argc, char **argv) @@ -1396,16 +1512,6 @@ err: return NULL; } -enum { - OPTION_OUTPUT = 256, - OPTION_BACKING_CHAIN = 257, -}; - -typedef enum OutputFormat { - OFORMAT_JSON, - OFORMAT_HUMAN, -} OutputFormat; - static int img_info(int argc, char **argv) { int c; diff --git a/qemu-img.texi b/qemu-img.texi index 00fca8da86..1a6c9e36f1 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -84,9 +84,10 @@ lists all snapshots in the given image Command description: @table @option -@item check [-f @var{fmt}] [-r [leaks | all]] @var{filename} +@item check [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] @var{filename} -Perform a consistency check on the disk image @var{filename}. +Perform a consistency check on the disk image @var{filename}. The command can +output in the format @var{ofmt} which is either @code{human} or @code{json}. If @code{-r} is specified, qemu-img tries to repair any inconsistencies found during the check. @code{-r leaks} repairs only cluster leaks, whereas From 801f70445293ec8ed2d78fd92313c2f71fa48ac9 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 7 Feb 2013 17:15:01 +0100 Subject: [PATCH 03/22] qcow2: introduce check_refcounts_l1/l2() flags The check_refcounts_l1/l2() functions have a check_copied argument to check that the QCOW_O_COPIED flag is consistent with refcount == 1. This should be a bool, not an int. However, the next patch introduces qcow2 fragmentation statistics and also needs to pass an option to check_refcounts_l1/l2(). This is a good opportunity to use an int flags field. Signed-off-by: Stefan Hajnoczi --- block/qcow2-refcount.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index d36cb4dd2f..4eec4b1228 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -914,6 +914,11 @@ static void inc_refcounts(BlockDriverState *bs, } } +/* Flags for check_refcounts_l1() and check_refcounts_l2() */ +enum { + CHECK_OFLAG_COPIED = 0x1, /* check QCOW_OFLAG_COPIED matches refcount */ +}; + /* * Increases the refcount in the given refcount table for the all clusters * referenced in the L2 table. While doing so, performs some checks on L2 @@ -924,7 +929,7 @@ static void inc_refcounts(BlockDriverState *bs, */ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, uint16_t *refcount_table, int refcount_table_size, int64_t l2_offset, - int check_copied) + int flags) { BDRVQcowState *s = bs->opaque; uint64_t *l2_table, l2_entry; @@ -971,7 +976,7 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, /* QCOW_OFLAG_COPIED must be set iff refcount == 1 */ uint64_t offset = l2_entry & L2E_OFFSET_MASK; - if (check_copied) { + if (flags & CHECK_OFLAG_COPIED) { refcount = get_refcount(bs, offset >> s->cluster_bits); if (refcount < 0) { fprintf(stderr, "Can't get refcount for offset %" @@ -1028,7 +1033,7 @@ static int check_refcounts_l1(BlockDriverState *bs, uint16_t *refcount_table, int refcount_table_size, int64_t l1_table_offset, int l1_size, - int check_copied) + int flags) { BDRVQcowState *s = bs->opaque; uint64_t *l1_table, l2_offset, l1_size2; @@ -1057,7 +1062,7 @@ static int check_refcounts_l1(BlockDriverState *bs, l2_offset = l1_table[i]; if (l2_offset) { /* QCOW_OFLAG_COPIED must be set iff refcount == 1 */ - if (check_copied) { + if (flags & CHECK_OFLAG_COPIED) { refcount = get_refcount(bs, (l2_offset & ~QCOW_OFLAG_COPIED) >> s->cluster_bits); if (refcount < 0) { @@ -1086,7 +1091,7 @@ static int check_refcounts_l1(BlockDriverState *bs, /* Process and check L2 entries */ ret = check_refcounts_l2(bs, res, refcount_table, - refcount_table_size, l2_offset, check_copied); + refcount_table_size, l2_offset, flags); if (ret < 0) { goto fail; } @@ -1128,7 +1133,8 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res, /* current L1 table */ ret = check_refcounts_l1(bs, res, refcount_table, nb_clusters, - s->l1_table_offset, s->l1_size, 1); + s->l1_table_offset, s->l1_size, + CHECK_OFLAG_COPIED); if (ret < 0) { goto fail; } From fba31bae2d776fb4134186a830a252523df7933f Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 7 Feb 2013 17:15:02 +0100 Subject: [PATCH 04/22] qcow2: record fragmentation statistics during check The qemu-img check command can display fragmentation statistics: * Total number of clusters in virtual disk * Number of allocated clusters * Number of fragmented clusters This patch adds fragmentation statistics support to qcow2. Compressed and normal clusters count as allocated. Zero clusters are not counted as allocated unless their L2 entry has a non-zero offset (e.g. preallocation). Only the current L1 table counts towards the statistics - snapshots are ignored. Signed-off-by: Stefan Hajnoczi --- block/qcow2-refcount.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index 4eec4b1228..771b7b2850 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -917,6 +917,7 @@ static void inc_refcounts(BlockDriverState *bs, /* Flags for check_refcounts_l1() and check_refcounts_l2() */ enum { CHECK_OFLAG_COPIED = 0x1, /* check QCOW_OFLAG_COPIED matches refcount */ + CHECK_FRAG_INFO = 0x2, /* update BlockFragInfo counters */ }; /* @@ -933,6 +934,7 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, { BDRVQcowState *s = bs->opaque; uint64_t *l2_table, l2_entry; + uint64_t next_contiguous_offset = 0; int i, l2_size, nb_csectors, refcount; /* Read L2 table from disk */ @@ -963,6 +965,17 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, l2_entry &= s->cluster_offset_mask; inc_refcounts(bs, res, refcount_table, refcount_table_size, l2_entry & ~511, nb_csectors * 512); + + if (flags & CHECK_FRAG_INFO) { + res->bfi.allocated_clusters++; + + /* Compressed clusters are fragmented by nature. Since they + * take up sub-sector space but we only have sector granularity + * I/O we need to re-read the same sectors even for adjacent + * compressed clusters. + */ + res->bfi.fragmented_clusters++; + } break; case QCOW2_CLUSTER_ZERO: @@ -990,6 +1003,15 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, } } + if (flags & CHECK_FRAG_INFO) { + res->bfi.allocated_clusters++; + if (next_contiguous_offset && + offset != next_contiguous_offset) { + res->bfi.fragmented_clusters++; + } + next_contiguous_offset = offset + s->cluster_size; + } + /* Mark cluster as used */ inc_refcounts(bs, res, refcount_table,refcount_table_size, offset, s->cluster_size); @@ -1125,6 +1147,7 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res, size = bdrv_getlength(bs->file); nb_clusters = size_to_clusters(s, size); + res->bfi.total_clusters = nb_clusters; refcount_table = g_malloc0(nb_clusters * sizeof(uint16_t)); /* header */ @@ -1134,7 +1157,7 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res, /* current L1 table */ ret = check_refcounts_l1(bs, res, refcount_table, nb_clusters, s->l1_table_offset, s->l1_size, - CHECK_OFLAG_COPIED); + CHECK_OFLAG_COPIED | CHECK_FRAG_INFO); if (ret < 0) { goto fail; } From c9fc50839863f05545caca92bb9fbead8b1c91bd Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 7 Feb 2013 17:15:03 +0100 Subject: [PATCH 05/22] qemu-img: fix missing space in qemu-img check output The qemu-img check fragmentation printf() is missing a space before the '=' sign. The human output is not guaranteed to be stable and we are not aware of screen scrapers, so add the missing space. Also fix the missing indentation of the printf() arguments. Signed-off-by: Stefan Hajnoczi --- qemu-img.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qemu-img.c b/qemu-img.c index 34249fee12..0e34bf0c90 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -427,10 +427,11 @@ static void dump_human_image_check(ImageCheck *check) } if (check->total_clusters != 0 && check->allocated_clusters != 0) { - printf("%" PRId64 "/%" PRId64 "= %0.2f%% allocated, %0.2f%% fragmented\n", - check->allocated_clusters, check->total_clusters, - check->allocated_clusters * 100.0 / check->total_clusters, - check->fragmented_clusters * 100.0 / check->allocated_clusters); + printf("%" PRId64 "/%" PRId64 " = %0.2f%% allocated, " + "%0.2f%% fragmented\n", + check->allocated_clusters, check->total_clusters, + check->allocated_clusters * 100.0 / check->total_clusters, + check->fragmented_clusters * 100.0 / check->allocated_clusters); } if (check->image_end_offset) { From e6439d783cce2c5cdbe4f8028f0b45162b540f82 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 7 Feb 2013 17:15:04 +0100 Subject: [PATCH 06/22] qemu-img: add compressed clusters to BlockFragInfo Show how many clusters are compressed. This can be used to monitor how many compressed clusters remain and whether to recompress the image. Suggested-by: Cole Robinson Signed-off-by: Stefan Hajnoczi --- include/block/block.h | 1 + qapi-schema.json | 6 +++++- qemu-img.c | 7 +++++-- tests/qemu-iotests/044.out | 1 + tests/qemu-iotests/common.rc | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/include/block/block.h b/include/block/block.h index ce61883fc9..9661f9aa46 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -24,6 +24,7 @@ typedef struct BlockFragInfo { uint64_t allocated_clusters; uint64_t total_clusters; uint64_t fragmented_clusters; + uint64_t compressed_clusters; } BlockFragInfo; typedef struct QEMUSnapshotInfo { diff --git a/qapi-schema.json b/qapi-schema.json index c20725c7f7..28b070f16b 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -279,6 +279,10 @@ # field is present if the driver for the image format # supports it # +# @compressed-clusters: #optional total number of compressed clusters, this +# field is present if the driver for the image format +# supports it +# # Since: 1.4 # ## @@ -288,7 +292,7 @@ '*image-end-offset': 'int', '*corruptions': 'int', '*leaks': 'int', '*corruptions-fixed': 'int', '*leaks-fixed': 'int', '*total-clusters': 'int', '*allocated-clusters': 'int', - '*fragmented-clusters': 'int' } } + '*fragmented-clusters': 'int', '*compressed-clusters': 'int' } } ## # @StatusInfo: diff --git a/qemu-img.c b/qemu-img.c index 0e34bf0c90..fa9b2af79d 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -428,10 +428,11 @@ static void dump_human_image_check(ImageCheck *check) if (check->total_clusters != 0 && check->allocated_clusters != 0) { printf("%" PRId64 "/%" PRId64 " = %0.2f%% allocated, " - "%0.2f%% fragmented\n", + "%0.2f%% fragmented, %0.2f%% compressed clusters\n", check->allocated_clusters, check->total_clusters, check->allocated_clusters * 100.0 / check->total_clusters, - check->fragmented_clusters * 100.0 / check->allocated_clusters); + check->fragmented_clusters * 100.0 / check->allocated_clusters, + check->compressed_clusters * 100.0 / check->allocated_clusters); } if (check->image_end_offset) { @@ -472,6 +473,8 @@ static int collect_image_check(BlockDriverState *bs, check->has_allocated_clusters = result.bfi.allocated_clusters != 0; check->fragmented_clusters = result.bfi.fragmented_clusters; check->has_fragmented_clusters = result.bfi.fragmented_clusters != 0; + check->compressed_clusters = result.bfi.compressed_clusters; + check->has_compressed_clusters = result.bfi.compressed_clusters != 0; return 0; } diff --git a/tests/qemu-iotests/044.out b/tests/qemu-iotests/044.out index 9c4867329f..5eed3f87a3 100644 --- a/tests/qemu-iotests/044.out +++ b/tests/qemu-iotests/044.out @@ -1,4 +1,5 @@ No errors were found on the image. +7292415/8391499= 86.90% allocated, 0.00% fragmented, 0.00% compressed clusters Image end offset: 4296447488 . ---------------------------------------------------------------------- diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc index 5ba960b09f..e522d617aa 100644 --- a/tests/qemu-iotests/common.rc +++ b/tests/qemu-iotests/common.rc @@ -162,7 +162,7 @@ _cleanup_test_img() _check_test_img() { $QEMU_IMG check "$@" -f $IMGFMT $TEST_IMG 2>&1 | \ - sed -e "/fragmented$/d" \ + sed -e '/allocated.*fragmented.*compressed clusters/d' \ -e 's/qemu-img: This image format does not support checks/No errors were found on the image./' \ -e '/Image end offset: [0-9]\+/d' } From 4db35162ea54d84c29074adfcff470ee2687e7b9 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 7 Feb 2013 17:15:05 +0100 Subject: [PATCH 07/22] qcow2: support compressed clusters in BlockFragInfo Signed-off-by: Stefan Hajnoczi --- block/qcow2-refcount.c | 1 + 1 file changed, 1 insertion(+) diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index 771b7b2850..55543edf77 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -968,6 +968,7 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, if (flags & CHECK_FRAG_INFO) { res->bfi.allocated_clusters++; + res->bfi.compressed_clusters++; /* Compressed clusters are fragmented by nature. Since they * take up sub-sector space but we only have sector granularity From c546194f260fb3e391193cb8cc33505618077ecb Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Wed, 13 Feb 2013 16:53:42 +0100 Subject: [PATCH 08/22] block: use Error in do_check_io_limits() The do_check_io_limits() function returns false when I/O limits are invalid but it doesn't set an Error to indicate why. The two do_check_io_limits() callers duplicate error reporting. Solve this by passing an Error pointer into do_check_io_limits(). Note that the two callers report slightly different errors: drive_init() prints a custom error message while qmp_block_set_io_throttle() does error_set(errp, QERR_INVALID_PARAMETER_COMBINATION). QERR_INVALID_PARAMETER_COMBINATION is a generic error, see include/qapi/qmp/qerror.h: #define QERR_INVALID_PARAMETER_COMBINATION \ ERROR_CLASS_GENERIC_ERROR, "Invalid parameter combination" Since it is generic we are not obliged to keep this error. Switch to the custom error message which contains more information. This patch prepares for adding additional checks with their own error messages to do_check_io_limits(). The next patch adds a new check. Reviewed-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- blockdev.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/blockdev.c b/blockdev.c index 63e6f1eafa..9b0351343e 100644 --- a/blockdev.c +++ b/blockdev.c @@ -255,7 +255,7 @@ static int parse_block_error_action(const char *buf, bool is_read) } } -static bool do_check_io_limits(BlockIOLimit *io_limits) +static bool do_check_io_limits(BlockIOLimit *io_limits, Error **errp) { bool bps_flag; bool iops_flag; @@ -269,6 +269,8 @@ static bool do_check_io_limits(BlockIOLimit *io_limits) && ((io_limits->iops[BLOCK_IO_LIMIT_READ] != 0) || (io_limits->iops[BLOCK_IO_LIMIT_WRITE] != 0)); if (bps_flag || iops_flag) { + error_setg(errp, "bps(iops) and bps_rd/bps_wr(iops_rd/iops_wr) " + "cannot be used at the same time"); return false; } @@ -297,6 +299,7 @@ DriveInfo *drive_init(QemuOpts *opts, BlockInterfaceType block_default_type) int snapshot = 0; bool copy_on_read; int ret; + Error *error = NULL; translation = BIOS_ATA_TRANSLATION_AUTO; media = MEDIA_DISK; @@ -427,9 +430,9 @@ DriveInfo *drive_init(QemuOpts *opts, BlockInterfaceType block_default_type) io_limits.iops[BLOCK_IO_LIMIT_WRITE] = qemu_opt_get_number(opts, "iops_wr", 0); - if (!do_check_io_limits(&io_limits)) { - error_report("bps(iops) and bps_rd/bps_wr(iops_rd/iops_wr) " - "cannot be used at the same time"); + if (!do_check_io_limits(&io_limits, &error)) { + error_report("%s", error_get_pretty(error)); + error_free(error); return NULL; } @@ -975,8 +978,7 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd, io_limits.iops[BLOCK_IO_LIMIT_READ] = iops_rd; io_limits.iops[BLOCK_IO_LIMIT_WRITE]= iops_wr; - if (!do_check_io_limits(&io_limits)) { - error_set(errp, QERR_INVALID_PARAMETER_COMBINATION); + if (!do_check_io_limits(&io_limits, errp)) { return; } From 7d81c1413c9c9bdcc966453636e4ca7776b59861 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Wed, 13 Feb 2013 16:53:43 +0100 Subject: [PATCH 09/22] block: refuse negative iops and bps values Negative I/O throttling iops and bps values do not make sense so reject them with an error message. Reviewed-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- blockdev.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/blockdev.c b/blockdev.c index 9b0351343e..ba3759c849 100644 --- a/blockdev.c +++ b/blockdev.c @@ -274,6 +274,16 @@ static bool do_check_io_limits(BlockIOLimit *io_limits, Error **errp) return false; } + if (io_limits->bps[BLOCK_IO_LIMIT_TOTAL] < 0 || + io_limits->bps[BLOCK_IO_LIMIT_WRITE] < 0 || + io_limits->bps[BLOCK_IO_LIMIT_READ] < 0 || + io_limits->iops[BLOCK_IO_LIMIT_TOTAL] < 0 || + io_limits->iops[BLOCK_IO_LIMIT_WRITE] < 0 || + io_limits->iops[BLOCK_IO_LIMIT_READ] < 0) { + error_setg(errp, "bps and iops values must be 0 or greater"); + return false; + } + return true; } From b35b2bba5b372ff912c5fe7e5ad6c5a9d883464f Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Wed, 13 Feb 2013 09:09:39 +0100 Subject: [PATCH 10/22] block: Add synchronous wrapper for bdrv_co_is_allocated_above There's no synchronous wrapper for bdrv_co_is_allocated_above function so it's not possible to check for sector allocation in an image with a backing file. Signed-off-by: Miroslav Rezanina Reviewed-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- block.c | 39 +++++++++++++++++++++++++++++++++++++++ include/block/block.h | 2 ++ 2 files changed, 41 insertions(+) diff --git a/block.c b/block.c index 50dab8e595..08039d2f32 100644 --- a/block.c +++ b/block.c @@ -2681,6 +2681,7 @@ int bdrv_has_zero_init(BlockDriverState *bs) typedef struct BdrvCoIsAllocatedData { BlockDriverState *bs; + BlockDriverState *base; int64_t sector_num; int nb_sectors; int *pnum; @@ -2813,6 +2814,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; +} + BlockInfo *bdrv_query_info(BlockDriverState *bs) { BlockInfo *info = g_malloc0(sizeof(*info)); diff --git a/include/block/block.h b/include/block/block.h index 9661f9aa46..94d84b409d 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -280,6 +280,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, BlockdevOnError on_read_error, BlockdevOnError on_write_error); From f382d43a9180ed20c671dc058d5452c2df7d3c61 Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Wed, 13 Feb 2013 09:09:40 +0100 Subject: [PATCH 11/22] qemu-img: Add "Quiet mode" option There can be a need to turn output to stdout off. This patch adds a -q option that enable "Quiet mode". In Quiet mode, only errors are printed out. Signed-off-by: Miroslav Rezanina Reviewed-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- block.c | 12 ++-- blockdev.c | 6 +- include/block/block.h | 3 +- qemu-img-cmds.hx | 28 ++++---- qemu-img.c | 154 +++++++++++++++++++++++++++++------------- qemu-img.texi | 3 + 6 files changed, 135 insertions(+), 71 deletions(-) diff --git a/block.c b/block.c index 08039d2f32..a4d7125eec 100644 --- a/block.c +++ b/block.c @@ -4470,7 +4470,8 @@ bdrv_acct_done(BlockDriverState *bs, BlockAcctCookie *cookie) void bdrv_img_create(const char *filename, const char *fmt, const char *base_filename, const char *base_fmt, - char *options, uint64_t img_size, int flags, Error **errp) + char *options, uint64_t img_size, int flags, + Error **errp, bool quiet) { QEMUOptionParameter *param = NULL, *create_options = NULL; QEMUOptionParameter *backing_fmt, *backing_file, *size; @@ -4579,10 +4580,11 @@ void bdrv_img_create(const char *filename, const char *fmt, } } - printf("Formatting '%s', fmt=%s ", filename, fmt); - print_option_parameters(param); - puts(""); - + if (!quiet) { + printf("Formatting '%s', fmt=%s ", filename, fmt); + print_option_parameters(param); + puts(""); + } ret = bdrv_create(drv, filename, param); if (ret < 0) { if (ret == -ENOTSUP) { diff --git a/blockdev.c b/blockdev.c index ba3759c849..b307ed9ba9 100644 --- a/blockdev.c +++ b/blockdev.c @@ -804,7 +804,7 @@ void qmp_transaction(BlockdevActionList *dev_list, Error **errp) bdrv_img_create(new_image_file, format, states->old_bs->filename, states->old_bs->drv->format_name, - NULL, -1, flags, &local_err); + NULL, -1, flags, &local_err, false); if (error_is_set(&local_err)) { error_propagate(errp, local_err); goto delete_and_fail; @@ -1296,7 +1296,7 @@ void qmp_drive_mirror(const char *device, const char *target, /* create new image w/o backing file */ assert(format && drv); bdrv_img_create(target, format, - NULL, NULL, NULL, size, flags, &local_err); + NULL, NULL, NULL, size, flags, &local_err, false); } else { switch (mode) { case NEW_IMAGE_MODE_EXISTING: @@ -1307,7 +1307,7 @@ void qmp_drive_mirror(const char *device, const char *target, bdrv_img_create(target, format, source->filename, source->drv->format_name, - NULL, size, flags, &local_err); + NULL, size, flags, &local_err, false); break; default: abort(); diff --git a/include/block/block.h b/include/block/block.h index 94d84b409d..b4a24da3e9 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -353,7 +353,8 @@ int bdrv_load_vmstate(BlockDriverState *bs, uint8_t *buf, void bdrv_img_create(const char *filename, const char *fmt, const char *base_filename, const char *base_fmt, - char *options, uint64_t img_size, int flags, Error **errp); + char *options, uint64_t img_size, int flags, + Error **errp, bool quiet); void bdrv_set_buffer_alignment(BlockDriverState *bs, int align); void *qemu_blockalign(BlockDriverState *bs, size_t size); diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 259fc142ed..92837765b9 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -10,27 +10,27 @@ STEXI ETEXI DEF("check", img_check, - "check [-f fmt] [--output=ofmt] [-r [leaks | all]] filename") + "check [-q] [-f fmt] [--output=ofmt] [-r [leaks | all]] filename") STEXI -@item check [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] @var{filename} +@item check [-q] [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] @var{filename} ETEXI DEF("create", img_create, - "create [-f fmt] [-o options] filename [size]") + "create [-q] [-f fmt] [-o options] filename [size]") STEXI -@item create [-f @var{fmt}] [-o @var{options}] @var{filename} [@var{size}] +@item create [-q] [-f @var{fmt}] [-o @var{options}] @var{filename} [@var{size}] ETEXI DEF("commit", img_commit, - "commit [-f fmt] [-t cache] filename") + "commit [-q] [-f fmt] [-t cache] filename") STEXI -@item commit [-f @var{fmt}] [-t @var{cache}] @var{filename} +@item commit [-q] [-f @var{fmt}] [-t @var{cache}] @var{filename} 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") + "convert [-c] [-p] [-q] [-f fmt] [-t cache] [-O output_fmt] [-o options] [-s snapshot_name] [-S sparse_size] filename [filename2 [...]] output_filename") STEXI -@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} +@item convert [-c] [-p] [-q] [-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} ETEXI DEF("info", img_info, @@ -40,20 +40,20 @@ STEXI ETEXI DEF("snapshot", img_snapshot, - "snapshot [-l | -a snapshot | -c snapshot | -d snapshot] filename") + "snapshot [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename") STEXI -@item snapshot [-l | -a @var{snapshot} | -c @var{snapshot} | -d @var{snapshot}] @var{filename} +@item snapshot [-q] [-l | -a @var{snapshot} | -c @var{snapshot} | -d @var{snapshot}] @var{filename} ETEXI DEF("rebase", img_rebase, - "rebase [-f fmt] [-t cache] [-p] [-u] -b backing_file [-F backing_fmt] filename") + "rebase [-q] [-f fmt] [-t cache] [-p] [-u] -b backing_file [-F backing_fmt] filename") STEXI -@item rebase [-f @var{fmt}] [-t @var{cache}] [-p] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename} +@item rebase [-q] [-f @var{fmt}] [-t @var{cache}] [-p] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename} ETEXI DEF("resize", img_resize, - "resize filename [+ | -]size") + "resize [-q] filename [+ | -]size") STEXI -@item resize @var{filename} [+ | -]@var{size} +@item resize [-q] @var{filename} [+ | -]@var{size} @end table ETEXI diff --git a/qemu-img.c b/qemu-img.c index fa9b2af79d..3e2996e3c3 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -32,6 +32,7 @@ #include "block/block_int.h" #include #include +#include #ifdef _WIN32 #include @@ -96,6 +97,7 @@ static void help(void) " rebasing in this case (useful for renaming the backing file)\n" " '-h' with or without a command shows this help and lists the supported formats\n" " '-p' show progress of command (only certain commands)\n" + " '-q' use Quiet mode - do not print any output (except errors)\n" " '-S' indicates the consecutive number of bytes that must contain only zeros\n" " for qemu-img to create a sparse image during conversion\n" " '--output' takes the format in which the output must be done (human or json)\n" @@ -119,6 +121,18 @@ static void help(void) exit(1); } +static int qprintf(bool quiet, const char *fmt, ...) +{ + int ret = 0; + if (!quiet) { + va_list args; + va_start(args, fmt); + ret = vprintf(fmt, args); + va_end(args); + } + return ret; +} + #if defined(WIN32) /* XXX: put correct support for win32 */ static int read_password(char *buf, int buf_size) @@ -237,7 +251,8 @@ static int print_block_option_help(const char *filename, const char *fmt) static BlockDriverState *bdrv_new_open(const char *filename, const char *fmt, int flags, - bool require_io) + bool require_io, + bool quiet) { BlockDriverState *bs; BlockDriver *drv; @@ -263,7 +278,7 @@ static BlockDriverState *bdrv_new_open(const char *filename, } if (bdrv_is_encrypted(bs) && require_io) { - printf("Disk image '%s' is encrypted.\n", filename); + qprintf(quiet, "Disk image '%s' is encrypted.\n", filename); if (read_password(password, sizeof(password)) < 0) { error_report("No password given"); goto fail; @@ -312,9 +327,10 @@ static int img_create(int argc, char **argv) const char *base_filename = NULL; char *options = NULL; Error *local_err = NULL; + bool quiet = false; for(;;) { - c = getopt(argc, argv, "F:b:f:he6o:"); + c = getopt(argc, argv, "F:b:f:he6o:q"); if (c == -1) { break; } @@ -343,6 +359,9 @@ static int img_create(int argc, char **argv) case 'o': options = optarg; break; + case 'q': + quiet = true; + break; } } @@ -375,7 +394,7 @@ static int img_create(int argc, char **argv) } bdrv_img_create(filename, fmt, base_filename, base_fmt, - options, img_size, BDRV_O_FLAGS, &local_err); + options, img_size, BDRV_O_FLAGS, &local_err, quiet); if (error_is_set(&local_err)) { error_report("%s", error_get_pretty(local_err)); error_free(local_err); @@ -385,7 +404,7 @@ static int img_create(int argc, char **argv) return 0; } -static void dump_json_image_check(ImageCheck *check) +static void dump_json_image_check(ImageCheck *check, bool quiet) { Error *errp = NULL; QString *str; @@ -396,47 +415,52 @@ static void dump_json_image_check(ImageCheck *check) obj = qmp_output_get_qobject(ov); str = qobject_to_json_pretty(obj); assert(str != NULL); - printf("%s\n", qstring_get_str(str)); + qprintf(quiet, "%s\n", qstring_get_str(str)); qobject_decref(obj); qmp_output_visitor_cleanup(ov); QDECREF(str); } -static void dump_human_image_check(ImageCheck *check) +static void dump_human_image_check(ImageCheck *check, bool quiet) { if (!(check->corruptions || check->leaks || check->check_errors)) { - printf("No errors were found on the image.\n"); + qprintf(quiet, "No errors were found on the image.\n"); } else { if (check->corruptions) { - printf("\n%" PRId64 " errors were found on the image.\n" - "Data may be corrupted, or further writes to the image " - "may corrupt it.\n", - check->corruptions); + qprintf(quiet, "\n%" PRId64 " errors were found on the image.\n" + "Data may be corrupted, or further writes to the image " + "may corrupt it.\n", + check->corruptions); } if (check->leaks) { - printf("\n%" PRId64 " leaked clusters were found on the image.\n" - "This means waste of disk space, but no harm to data.\n", - check->leaks); + qprintf(quiet, + "\n%" PRId64 " leaked clusters were found on the image.\n" + "This means waste of disk space, but no harm to data.\n", + check->leaks); } if (check->check_errors) { - printf("\n%" PRId64 " internal errors have occurred during the check.\n", - check->check_errors); + qprintf(quiet, + "\n%" PRId64 + " internal errors have occurred during the check.\n", + check->check_errors); } } if (check->total_clusters != 0 && check->allocated_clusters != 0) { - printf("%" PRId64 "/%" PRId64 " = %0.2f%% allocated, " - "%0.2f%% fragmented, %0.2f%% compressed clusters\n", - check->allocated_clusters, check->total_clusters, - check->allocated_clusters * 100.0 / check->total_clusters, - check->fragmented_clusters * 100.0 / check->allocated_clusters, - check->compressed_clusters * 100.0 / check->allocated_clusters); + qprintf(quiet, "%" PRId64 "/%" PRId64 " = %0.2f%% allocated, " + "%0.2f%% fragmented, %0.2f%% compressed clusters\n", + check->allocated_clusters, check->total_clusters, + check->allocated_clusters * 100.0 / check->total_clusters, + check->fragmented_clusters * 100.0 / check->allocated_clusters, + check->compressed_clusters * 100.0 / + check->allocated_clusters); } if (check->image_end_offset) { - printf("Image end offset: %" PRId64 "\n", check->image_end_offset); + qprintf(quiet, + "Image end offset: %" PRId64 "\n", check->image_end_offset); } } @@ -496,6 +520,7 @@ static int img_check(int argc, char **argv) int fix = 0; int flags = BDRV_O_FLAGS | BDRV_O_CHECK; ImageCheck *check; + bool quiet = false; fmt = NULL; output = NULL; @@ -508,7 +533,7 @@ static int img_check(int argc, char **argv) {"output", required_argument, 0, OPTION_OUTPUT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, "f:hr:", + c = getopt_long(argc, argv, "f:hr:q", long_options, &option_index); if (c == -1) { break; @@ -535,6 +560,9 @@ static int img_check(int argc, char **argv) case OPTION_OUTPUT: output = optarg; break; + case 'q': + quiet = true; + break; } } if (optind >= argc) { @@ -551,7 +579,7 @@ static int img_check(int argc, char **argv) return 1; } - bs = bdrv_new_open(filename, fmt, flags, true); + bs = bdrv_new_open(filename, fmt, flags, true, quiet); if (!bs) { return 1; } @@ -574,12 +602,13 @@ static int img_check(int argc, char **argv) corruptions_fixed = check->corruptions_fixed; if (output_format == OFORMAT_HUMAN) { - printf("The following inconsistencies were found and repaired:\n\n" - " %" PRId64 " leaked clusters\n" - " %" PRId64 " corruptions\n\n" - "Double checking the fixed image now...\n", - check->leaks_fixed, - check->corruptions_fixed); + qprintf(quiet, + "The following inconsistencies were found and repaired:\n\n" + " %" PRId64 " leaked clusters\n" + " %" PRId64 " corruptions\n\n" + "Double checking the fixed image now...\n", + check->leaks_fixed, + check->corruptions_fixed); } ret = collect_image_check(bs, check, filename, fmt, 0); @@ -590,10 +619,10 @@ static int img_check(int argc, char **argv) switch (output_format) { case OFORMAT_HUMAN: - dump_human_image_check(check); + dump_human_image_check(check, quiet); break; case OFORMAT_JSON: - dump_json_image_check(check); + dump_json_image_check(check, quiet); break; } @@ -622,11 +651,12 @@ static int img_commit(int argc, char **argv) int c, ret, flags; const char *filename, *fmt, *cache; BlockDriverState *bs; + bool quiet = false; fmt = NULL; cache = BDRV_DEFAULT_CACHE; for(;;) { - c = getopt(argc, argv, "f:ht:"); + c = getopt(argc, argv, "f:ht:q"); if (c == -1) { break; } @@ -641,6 +671,9 @@ static int img_commit(int argc, char **argv) case 't': cache = optarg; break; + case 'q': + quiet = true; + break; } } if (optind >= argc) { @@ -655,14 +688,14 @@ static int img_commit(int argc, char **argv) return -1; } - bs = bdrv_new_open(filename, fmt, flags, true); + bs = bdrv_new_open(filename, fmt, flags, true, quiet); if (!bs) { return 1; } ret = bdrv_commit(bs); switch(ret) { case 0: - printf("Image committed.\n"); + qprintf(quiet, "Image committed.\n"); break; case -ENOENT: error_report("No disk inserted"); @@ -805,6 +838,7 @@ static int img_convert(int argc, char **argv) const char *snapshot_name = NULL; float local_progress = 0; int min_sparse = 8; /* Need at least 4k of zeros for sparse detection */ + bool quiet = false; fmt = NULL; out_fmt = "raw"; @@ -812,7 +846,7 @@ static int img_convert(int argc, char **argv) out_baseimg = NULL; compress = 0; for(;;) { - c = getopt(argc, argv, "f:O:B:s:hce6o:pS:t:"); + c = getopt(argc, argv, "f:O:B:s:hce6o:pS:t:q"); if (c == -1) { break; } @@ -866,9 +900,16 @@ static int img_convert(int argc, char **argv) case 't': cache = optarg; break; + case 'q': + quiet = true; + break; } } + if (quiet) { + progress = 0; + } + bs_n = argc - optind - 1; if (bs_n < 1) { help(); @@ -897,7 +938,8 @@ static int img_convert(int argc, char **argv) total_sectors = 0; for (bs_i = 0; bs_i < bs_n; bs_i++) { - bs[bs_i] = bdrv_new_open(argv[optind + bs_i], fmt, BDRV_O_FLAGS, true); + bs[bs_i] = bdrv_new_open(argv[optind + bs_i], fmt, BDRV_O_FLAGS, true, + quiet); if (!bs[bs_i]) { error_report("Could not open '%s'", argv[optind + bs_i]); ret = -1; @@ -1016,7 +1058,7 @@ static int img_convert(int argc, char **argv) return -1; } - out_bs = bdrv_new_open(out_filename, out_fmt, flags, true); + out_bs = bdrv_new_open(out_filename, out_fmt, flags, true, quiet); if (!out_bs) { ret = -1; goto out; @@ -1479,7 +1521,7 @@ static ImageInfoList *collect_image_info_list(const char *filename, g_hash_table_insert(filenames, (gpointer)filename, NULL); bs = bdrv_new_open(filename, fmt, BDRV_O_FLAGS | BDRV_O_NO_BACKING, - false); + false, false); if (!bs) { goto err; } @@ -1605,11 +1647,12 @@ static int img_snapshot(int argc, char **argv) int c, ret = 0, bdrv_oflags; int action = 0; qemu_timeval tv; + bool quiet = false; bdrv_oflags = BDRV_O_FLAGS | BDRV_O_RDWR; /* Parse commandline parameters */ for(;;) { - c = getopt(argc, argv, "la:c:d:h"); + c = getopt(argc, argv, "la:c:d:hq"); if (c == -1) { break; } @@ -1650,6 +1693,9 @@ static int img_snapshot(int argc, char **argv) action = SNAPSHOT_DELETE; snapshot_name = optarg; break; + case 'q': + quiet = true; + break; } } @@ -1659,7 +1705,7 @@ static int img_snapshot(int argc, char **argv) filename = argv[optind++]; /* Open the image */ - bs = bdrv_new_open(filename, NULL, bdrv_oflags, true); + bs = bdrv_new_open(filename, NULL, bdrv_oflags, true, quiet); if (!bs) { return 1; } @@ -1719,6 +1765,7 @@ static int img_rebase(int argc, char **argv) int c, flags, ret; int unsafe = 0; int progress = 0; + bool quiet = false; /* Parse commandline parameters */ fmt = NULL; @@ -1726,7 +1773,7 @@ static int img_rebase(int argc, char **argv) out_baseimg = NULL; out_basefmt = NULL; for(;;) { - c = getopt(argc, argv, "uhf:F:b:pt:"); + c = getopt(argc, argv, "uhf:F:b:pt:q"); if (c == -1) { break; } @@ -1753,9 +1800,16 @@ static int img_rebase(int argc, char **argv) case 't': cache = optarg; break; + case 'q': + quiet = true; + break; } } + if (quiet) { + progress = 0; + } + if ((optind >= argc) || (!unsafe && !out_baseimg)) { help(); } @@ -1777,7 +1831,7 @@ static int img_rebase(int argc, char **argv) * Ignore the old backing file for unsafe rebase in case we want to correct * the reference to a renamed or moved backing file. */ - bs = bdrv_new_open(filename, fmt, flags, true); + bs = bdrv_new_open(filename, fmt, flags, true, quiet); if (!bs) { return 1; } @@ -1989,6 +2043,7 @@ static int img_resize(int argc, char **argv) int c, ret, relative; const char *filename, *fmt, *size; int64_t n, total_size; + bool quiet = false; BlockDriverState *bs = NULL; QemuOpts *param; static QemuOptsList resize_options = { @@ -2017,7 +2072,7 @@ static int img_resize(int argc, char **argv) /* Parse getopt arguments */ fmt = NULL; for(;;) { - c = getopt(argc, argv, "f:h"); + c = getopt(argc, argv, "f:hq"); if (c == -1) { break; } @@ -2029,6 +2084,9 @@ static int img_resize(int argc, char **argv) case 'f': fmt = optarg; break; + case 'q': + quiet = true; + break; } } if (optind >= argc) { @@ -2062,7 +2120,7 @@ static int img_resize(int argc, char **argv) n = qemu_opt_get_size(param, BLOCK_OPT_SIZE, 0); qemu_opts_del(param); - bs = bdrv_new_open(filename, fmt, BDRV_O_FLAGS | BDRV_O_RDWR, true); + bs = bdrv_new_open(filename, fmt, BDRV_O_FLAGS | BDRV_O_RDWR, true, quiet); if (!bs) { ret = -1; goto out; @@ -2082,7 +2140,7 @@ static int img_resize(int argc, char **argv) ret = bdrv_truncate(bs, total_size); switch (ret) { case 0: - printf("Image resized.\n"); + qprintf(quiet, "Image resized.\n"); break; case -ENOTSUP: error_report("This image does not support resize"); diff --git a/qemu-img.texi b/qemu-img.texi index 1a6c9e36f1..43430fbea1 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -54,6 +54,9 @@ indicates that target image must be compressed (qcow format only) with or without a command shows help and lists the supported formats @item -p display progress bar (convert and rebase commands only) +@item -q +Quiet mode - do not print any output (except errors). There's no progress bar +in case both @var{-q} and @var{-p} options are used. @item -S @var{size} indicates the consecutive number of bytes that must contain only zeros for qemu-img to create a sparse image during conversion. This value is rounded From d14ed18c8d10a936e6f8b55f56afb4b75c305e10 Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Wed, 13 Feb 2013 09:09:41 +0100 Subject: [PATCH 12/22] qemu-img: Add compare subcommand This patch adds new qemu-img subcommand that compares content of two disk images. Signed-off-by: Miroslav Rezanina Reviewed-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- qemu-img-cmds.hx | 6 + qemu-img.c | 290 ++++++++++++++++++++++++++++++++++++++++++++++- qemu-img.texi | 53 +++++++++ 3 files changed, 348 insertions(+), 1 deletion(-) diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 92837765b9..4ca7e95655 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -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 diff --git a/qemu-img.c b/qemu-img.c index 3e2996e3c3..471de7d646 100644 --- a/qemu-img.c +++ b/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; diff --git a/qemu-img.texi b/qemu-img.texi index 43430fbea1..69f1bda6ae 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -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} From e930d201bc8066a314b9e115e4a2afca50f9c504 Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Wed, 13 Feb 2013 09:09:42 +0100 Subject: [PATCH 13/22] qemu-iotests: Add qemu-img compare test Simple test for qemu-img compare to check it's working correctly. Signed-off-by: Miroslav Rezanina Reviewed-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- tests/qemu-iotests/048 | 78 ++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/048.out | 31 +++++++++++++++ tests/qemu-iotests/group | 1 + 3 files changed, 110 insertions(+) create mode 100755 tests/qemu-iotests/048 create mode 100644 tests/qemu-iotests/048.out diff --git a/tests/qemu-iotests/048 b/tests/qemu-iotests/048 new file mode 100755 index 0000000000..7cce049d2d --- /dev/null +++ b/tests/qemu-iotests/048 @@ -0,0 +1,78 @@ +#!/bin/bash +## +## qemu-img compare test +## +## +## Copyright (C) 2013 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 . +## +# +# creator +owner=mrezanin@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +status=1 # failure is the default! + +_cleanup() +{ + echo "Cleanup" + _cleanup_test_img + rm ${TEST_IMG2} +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +_compare() +{ + $QEMU_IMG compare "$@" $TEST_IMG ${TEST_IMG2} + echo $? +} + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter +. ./common.pattern + +_supported_fmt raw qcow qcow2 qed +_supported_proto file +_supported_os Linux + +# Setup test basic parameters +TEST_IMG2=$TEST_IMG.2 +CLUSTER_SIZE=4096 +size=1024M + +_make_test_img $size +io_pattern write 524288 $CLUSTER_SIZE $CLUSTER_SIZE 4 45 + +# Compare identical images +cp $TEST_IMG ${TEST_IMG2} +_compare +_compare -q + +# Compare images with different size +$QEMU_IMG resize $TEST_IMG +512M +_compare +_compare -s + +# Compare images with different content +io_pattern write 1228800 $CLUSTER_SIZE 0 1 67 +_compare +io_pattern write 0 $CLUSTER_SIZE 0 1 123 +_compare + +# Cleanup +status=0 diff --git a/tests/qemu-iotests/048.out b/tests/qemu-iotests/048.out new file mode 100644 index 0000000000..68f65d5e19 --- /dev/null +++ b/tests/qemu-iotests/048.out @@ -0,0 +1,31 @@ +QA output created by 048 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824 +=== IO: pattern 45 +qemu-io> wrote 4096/4096 bytes at offset 524288 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io> wrote 4096/4096 bytes at offset 528384 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io> wrote 4096/4096 bytes at offset 532480 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io> wrote 4096/4096 bytes at offset 536576 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io> Images are identical. +0 +0 +Image resized. +Warning: Image size mismatch! +Images are identical. +0 +Strict mode: Image size mismatch! +1 +=== IO: pattern 67 +qemu-io> wrote 4096/4096 bytes at offset 1228800 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io> Content mismatch at offset 1228800! +1 +=== IO: pattern 123 +qemu-io> wrote 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io> Content mismatch at offset 0! +1 +Cleanup diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index 1bbd2bf929..1c0dfefd32 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -54,3 +54,4 @@ 045 rw auto 046 rw auto aio 047 rw auto +048 img auto quick From 4dc9f9d67dbf5d062d8db188b81cef435f291dd8 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Tue, 29 Jan 2013 10:46:52 +0100 Subject: [PATCH 14/22] qemu-iotests: Test qcow2 image creation options Just create lots of images and try out each of the creation options that qcow2 provides (except backing_file/fmt for now) I'm not totally happy with the behaviour of qemu-img in each of the cases, but let's be explicit and update the test when we do change things later. Signed-off-by: Kevin Wolf --- tests/qemu-iotests/049 | 123 +++++++++++++++++++++ tests/qemu-iotests/049.out | 212 +++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/group | 1 + 3 files changed, 336 insertions(+) create mode 100755 tests/qemu-iotests/049 create mode 100644 tests/qemu-iotests/049.out diff --git a/tests/qemu-iotests/049 b/tests/qemu-iotests/049 new file mode 100755 index 0000000000..6c6017e2d2 --- /dev/null +++ b/tests/qemu-iotests/049 @@ -0,0 +1,123 @@ +#!/bin/bash +# +# Check qemu-img option parsing +# +# Copyright (C) 2013 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 . +# + +# 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() +{ + _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 + +function filter_test_dir() +{ + sed -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \ + -e "s#$TEST_DIR#TEST_DIR#g" +} + +function test_qemu_img() +{ + echo qemu-img "$@" | filter_test_dir + $QEMU_IMG "$@" 2>&1 | filter_test_dir + echo +} + +echo "=== Check correct interpretation of suffixes for image size ===" +echo +sizes="1024 1024b 1k 1K 1M 1G 1T " +sizes+="1024.0 1024.0b 1.5k 1.5K 1.5M 1.5G 1.5T" + +echo "== 1. Traditional size parameter ==" +echo +for s in $sizes; do + test_qemu_img create -f $IMGFMT $TEST_IMG $s +done + +echo "== 2. Specifying size via -o ==" +echo +for s in $sizes; do + test_qemu_img create -f $IMGFMT -o size=$s $TEST_IMG +done + +echo "== 3. Invalid sizes ==" +echo +sizes="-1024 -1k 1kilobyte foobar" + +for s in $sizes; do + test_qemu_img create -f $IMGFMT $TEST_IMG -- $s + test_qemu_img create -f $IMGFMT -o size=$s $TEST_IMG +done + +echo "== Check correct interpretation of suffixes for cluster size ==" +echo +sizes="1024 1024b 1k 1K 1M " +sizes+="1024.0 1024.0b 0.5k 0.5K 0.5M" + +for s in $sizes; do + test_qemu_img create -f $IMGFMT -o cluster_size=$s $TEST_IMG 64M +done + +echo "== Check compat level option ==" +echo +test_qemu_img create -f $IMGFMT -o compat=0.10 $TEST_IMG 64M +test_qemu_img create -f $IMGFMT -o compat=1.1 $TEST_IMG 64M + +test_qemu_img create -f $IMGFMT -o compat=0.42 $TEST_IMG 64M +test_qemu_img create -f $IMGFMT -o compat=foobar $TEST_IMG 64M + +echo "== Check preallocation option ==" +echo +test_qemu_img create -f $IMGFMT -o preallocation=off $TEST_IMG 64M +test_qemu_img create -f $IMGFMT -o preallocation=metadata $TEST_IMG 64M +test_qemu_img create -f $IMGFMT -o preallocation=1234 $TEST_IMG 64M + +echo "== Check encryption option ==" +echo +test_qemu_img create -f $IMGFMT -o encryption=off $TEST_IMG 64M +test_qemu_img create -f $IMGFMT -o encryption=on $TEST_IMG 64M + +echo "== Check lazy_refcounts option (only with v3) ==" +echo +test_qemu_img create -f $IMGFMT -o compat=1.1,lazy_refcounts=off $TEST_IMG 64M +test_qemu_img create -f $IMGFMT -o compat=1.1,lazy_refcounts=on $TEST_IMG 64M + +test_qemu_img create -f $IMGFMT -o compat=0.10,lazy_refcounts=off $TEST_IMG 64M +test_qemu_img create -f $IMGFMT -o compat=0.10,lazy_refcounts=on $TEST_IMG 64M + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out new file mode 100644 index 0000000000..72db13f8d2 --- /dev/null +++ b/tests/qemu-iotests/049.out @@ -0,0 +1,212 @@ +QA output created by 049 +=== Check correct interpretation of suffixes for image size === + +== 1. Traditional size parameter == + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1024 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1024b +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1k +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1K +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1048576 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1G +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1073741824 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1T +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1099511627776 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1024.0 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1024.0b +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5k +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1536 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5K +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1536 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1572864 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5G +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1610612736 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5T +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1649267441664 encryption=off cluster_size=65536 lazy_refcounts=off + +== 2. Specifying size via -o == + +qemu-img create -f qcow2 -o size=1024 TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1024b TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1k TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1K TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1M TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1048576 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1G TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1073741824 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1T TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1099511627776 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1024.0 TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1024.0b TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1.5k TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1536 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1.5K TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1536 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1.5M TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1572864 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1.5G TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1610612736 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o size=1.5T TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1649267441664 encryption=off cluster_size=65536 lazy_refcounts=off + +== 3. Invalid sizes == + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1024 +qemu-img: Image size must be less than 8 EiB! + +qemu-img create -f qcow2 -o size=-1024 TEST_DIR/t.qcow2 +qemu-img: qcow2 doesn't support shrinking images yet +qemu-img: Formatting or formatting option not supported for file format 'qcow2' +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=-1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1k +qemu-img: Image size must be less than 8 EiB! + +qemu-img create -f qcow2 -o size=-1k TEST_DIR/t.qcow2 +qemu-img: qcow2 doesn't support shrinking images yet +qemu-img: Formatting or formatting option not supported for file format 'qcow2' +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=-1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- 1kilobyte +qemu-img: Invalid image size specified! You may use k, M, G or T suffixes for +qemu-img: kilobytes, megabytes, gigabytes and terabytes. + +qemu-img create -f qcow2 -o size=1kilobyte TEST_DIR/t.qcow2 +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- foobar +qemu-img: Invalid image size specified! You may use k, M, G or T suffixes for +qemu-img: kilobytes, megabytes, gigabytes and terabytes. + +qemu-img create -f qcow2 -o size=foobar TEST_DIR/t.qcow2 +qemu-img: Parameter 'size' expects a size +qemu-img: Invalid options for file format 'qcow2'. + +== Check correct interpretation of suffixes for cluster size == + +qemu-img create -f qcow2 -o cluster_size=1024 TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=1024 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=1024b TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=1024 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=1k TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=1024 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=1K TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=1024 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=1M TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=1048576 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=1024.0 TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=1024 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=1024.0b TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=1024 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=0.5k TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=512 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=0.5K TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=512 lazy_refcounts=off + +qemu-img create -f qcow2 -o cluster_size=0.5M TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=524288 lazy_refcounts=off + +== Check compat level option == + +qemu-img create -f qcow2 -o compat=0.10 TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat='0.10' encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o compat=1.1 TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat='1.1' encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o compat=0.42 TEST_DIR/t.qcow2 64M +Invalid compatibility level: '0.42' +qemu-img: TEST_DIR/t.qcow2: error while creating qcow2: Invalid argument +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat='0.42' encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o compat=foobar TEST_DIR/t.qcow2 64M +Invalid compatibility level: 'foobar' +qemu-img: TEST_DIR/t.qcow2: error while creating qcow2: Invalid argument +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat='foobar' encryption=off cluster_size=65536 lazy_refcounts=off + +== Check preallocation option == + +qemu-img create -f qcow2 -o preallocation=off TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 preallocation='off' lazy_refcounts=off + +qemu-img create -f qcow2 -o preallocation=metadata TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 preallocation='metadata' lazy_refcounts=off + +qemu-img create -f qcow2 -o preallocation=1234 TEST_DIR/t.qcow2 64M +Invalid preallocation mode: '1234' +qemu-img: TEST_DIR/t.qcow2: error while creating qcow2: Invalid argument +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 preallocation='1234' lazy_refcounts=off + +== Check encryption option == + +qemu-img create -f qcow2 -o encryption=off TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o encryption=on TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on cluster_size=65536 lazy_refcounts=off + +== Check lazy_refcounts option (only with v3) == + +qemu-img create -f qcow2 -o compat=1.1,lazy_refcounts=off TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat='1.1' encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o compat=1.1,lazy_refcounts=on TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat='1.1' encryption=off cluster_size=65536 lazy_refcounts=on + +qemu-img create -f qcow2 -o compat=0.10,lazy_refcounts=off TEST_DIR/t.qcow2 64M +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat='0.10' encryption=off cluster_size=65536 lazy_refcounts=off + +qemu-img create -f qcow2 -o compat=0.10,lazy_refcounts=on TEST_DIR/t.qcow2 64M +Lazy refcounts only supported with compatibility level 1.1 and above (use compat=1.1 or greater) +qemu-img: TEST_DIR/t.qcow2: error while creating qcow2: Invalid argument +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat='0.10' encryption=off cluster_size=65536 lazy_refcounts=on + +*** done diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index 1c0dfefd32..fcf57e0510 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -55,3 +55,4 @@ 046 rw auto aio 047 rw auto 048 img auto quick +049 rw auto From 402397843e20e35d6cb7c80837c7cfdb19ede591 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 19 Feb 2013 11:59:09 +0100 Subject: [PATCH 15/22] coroutine: move pooling to common code The coroutine pool code is duplicated between the ucontext and sigaltstack backends, and absent from the win32 backend. But the code can be shared easily by moving it to qemu-coroutine.c. Signed-off-by: Paolo Bonzini Reviewed-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- coroutine-sigaltstack.c | 43 +-------------------------------------- coroutine-ucontext.c | 43 +-------------------------------------- qemu-coroutine.c | 45 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 86 deletions(-) diff --git a/coroutine-sigaltstack.c b/coroutine-sigaltstack.c index e37ebac9c4..b4d176200c 100644 --- a/coroutine-sigaltstack.c +++ b/coroutine-sigaltstack.c @@ -33,15 +33,6 @@ #include "qemu-common.h" #include "block/coroutine_int.h" -enum { - /* Maximum free pool size prevents holding too many freed coroutines */ - POOL_MAX_SIZE = 64, -}; - -/** Free list to speed up creation */ -static QSLIST_HEAD(, Coroutine) pool = QSLIST_HEAD_INITIALIZER(pool); -static unsigned int pool_size; - typedef struct { Coroutine base; void *stack; @@ -85,17 +76,6 @@ static void qemu_coroutine_thread_cleanup(void *opaque) g_free(s); } -static void __attribute__((destructor)) coroutine_cleanup(void) -{ - Coroutine *co; - Coroutine *tmp; - - QSLIST_FOREACH_SAFE(co, &pool, pool_next, tmp) { - g_free(DO_UPCAST(CoroutineUContext, base, co)->stack); - g_free(co); - } -} - static void __attribute__((constructor)) coroutine_init(void) { int ret; @@ -164,7 +144,7 @@ static void coroutine_trampoline(int signal) coroutine_bootstrap(self, co); } -static Coroutine *coroutine_new(void) +Coroutine *qemu_coroutine_new(void) { const size_t stack_size = 1 << 20; CoroutineUContext *co; @@ -272,31 +252,10 @@ static Coroutine *coroutine_new(void) return &co->base; } -Coroutine *qemu_coroutine_new(void) -{ - Coroutine *co; - - co = QSLIST_FIRST(&pool); - if (co) { - QSLIST_REMOVE_HEAD(&pool, pool_next); - pool_size--; - } else { - co = coroutine_new(); - } - return co; -} - void qemu_coroutine_delete(Coroutine *co_) { CoroutineUContext *co = DO_UPCAST(CoroutineUContext, base, co_); - if (pool_size < POOL_MAX_SIZE) { - QSLIST_INSERT_HEAD(&pool, &co->base, pool_next); - co->base.caller = NULL; - pool_size++; - return; - } - g_free(co->stack); g_free(co); } diff --git a/coroutine-ucontext.c b/coroutine-ucontext.c index a9c30e9df4..6f8ffa85e3 100644 --- a/coroutine-ucontext.c +++ b/coroutine-ucontext.c @@ -34,15 +34,6 @@ #include #endif -enum { - /* Maximum free pool size prevents holding too many freed coroutines */ - POOL_MAX_SIZE = 64, -}; - -/** Free list to speed up creation */ -static QSLIST_HEAD(, Coroutine) pool = QSLIST_HEAD_INITIALIZER(pool); -static unsigned int pool_size; - typedef struct { Coroutine base; void *stack; @@ -96,17 +87,6 @@ static void qemu_coroutine_thread_cleanup(void *opaque) g_free(s); } -static void __attribute__((destructor)) coroutine_cleanup(void) -{ - Coroutine *co; - Coroutine *tmp; - - QSLIST_FOREACH_SAFE(co, &pool, pool_next, tmp) { - g_free(DO_UPCAST(CoroutineUContext, base, co)->stack); - g_free(co); - } -} - static void __attribute__((constructor)) coroutine_init(void) { int ret; @@ -140,7 +120,7 @@ static void coroutine_trampoline(int i0, int i1) } } -static Coroutine *coroutine_new(void) +Coroutine *qemu_coroutine_new(void) { const size_t stack_size = 1 << 20; CoroutineUContext *co; @@ -185,20 +165,6 @@ static Coroutine *coroutine_new(void) return &co->base; } -Coroutine *qemu_coroutine_new(void) -{ - Coroutine *co; - - co = QSLIST_FIRST(&pool); - if (co) { - QSLIST_REMOVE_HEAD(&pool, pool_next); - pool_size--; - } else { - co = coroutine_new(); - } - return co; -} - #ifdef CONFIG_VALGRIND_H #ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE /* Work around an unused variable in the valgrind.h macro... */ @@ -217,13 +183,6 @@ void qemu_coroutine_delete(Coroutine *co_) { CoroutineUContext *co = DO_UPCAST(CoroutineUContext, base, co_); - if (pool_size < POOL_MAX_SIZE) { - QSLIST_INSERT_HEAD(&pool, &co->base, pool_next); - co->base.caller = NULL; - pool_size++; - return; - } - #ifdef CONFIG_VALGRIND_H valgrind_stack_deregister(co); #endif diff --git a/qemu-coroutine.c b/qemu-coroutine.c index 0f6e268574..25a14c605d 100644 --- a/qemu-coroutine.c +++ b/qemu-coroutine.c @@ -17,13 +17,54 @@ #include "block/coroutine.h" #include "block/coroutine_int.h" +enum { + /* Maximum free pool size prevents holding too many freed coroutines */ + POOL_MAX_SIZE = 64, +}; + +/** Free list to speed up creation */ +static QSLIST_HEAD(, Coroutine) pool = QSLIST_HEAD_INITIALIZER(pool); +static unsigned int pool_size; + Coroutine *qemu_coroutine_create(CoroutineEntry *entry) { - Coroutine *co = qemu_coroutine_new(); + Coroutine *co; + + co = QSLIST_FIRST(&pool); + if (co) { + QSLIST_REMOVE_HEAD(&pool, pool_next); + pool_size--; + } else { + co = qemu_coroutine_new(); + } + co->entry = entry; return co; } +static void coroutine_delete(Coroutine *co) +{ + if (pool_size < POOL_MAX_SIZE) { + QSLIST_INSERT_HEAD(&pool, co, pool_next); + co->caller = NULL; + pool_size++; + return; + } + + qemu_coroutine_delete(co); +} + +static void __attribute__((destructor)) coroutine_cleanup(void) +{ + Coroutine *co; + Coroutine *tmp; + + QSLIST_FOREACH_SAFE(co, &pool, pool_next, tmp) { + QSLIST_REMOVE_HEAD(&pool, pool_next); + qemu_coroutine_delete(co); + } +} + static void coroutine_swap(Coroutine *from, Coroutine *to) { CoroutineAction ret; @@ -35,7 +76,7 @@ static void coroutine_swap(Coroutine *from, Coroutine *to) return; case COROUTINE_TERMINATE: trace_qemu_coroutine_terminate(to); - qemu_coroutine_delete(to); + coroutine_delete(to); return; default: abort(); From 027003152f4cf21952f9282b4487daf3fdd372ba Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 19 Feb 2013 11:59:10 +0100 Subject: [PATCH 16/22] coroutine: trim down nesting level in perf_nesting test 20000 nested coroutines require 20 GB of virtual address space. Only nest 1000 of them so that the test (only enabled with "-m perf" on the command line) runs on 32-bit machines too. Cc: qemu-stable@nongnu.org Signed-off-by: Paolo Bonzini Reviewed-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- tests/test-coroutine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-coroutine.c b/tests/test-coroutine.c index 4c6cc81fb9..39be046ec7 100644 --- a/tests/test-coroutine.c +++ b/tests/test-coroutine.c @@ -183,7 +183,7 @@ static void perf_nesting(void) double duration; maxcycles = 100000000; - maxnesting = 20000; + maxnesting = 1000; Coroutine *root; NestData nd = { .n_enter = 0, From 9a665b2b8640e464f0a778216fc2dca8d02acf33 Mon Sep 17 00:00:00 2001 From: Peter Lieven Date: Mon, 18 Feb 2013 13:48:31 +0100 Subject: [PATCH 17/22] block: complete all IOs before .bdrv_truncate bdrv_truncate() invalidates the bdrv_check_request() result for in-flight requests, so there should better be none. Cc: qemu-stable@nongnu.org Signed-off-by: Peter Lieven Reported-by: Kevin Wolf Reviewed-by: Paolo Bonzini Signed-off-by: Kevin Wolf --- block.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/block.c b/block.c index a4d7125eec..f184b376da 100644 --- a/block.c +++ b/block.c @@ -2427,6 +2427,10 @@ int bdrv_truncate(BlockDriverState *bs, int64_t offset) return -EACCES; if (bdrv_in_use(bs)) return -EBUSY; + + /* There better not be any in-flight IOs when we truncate the device. */ + bdrv_drain_all(); + ret = drv->bdrv_truncate(bs, offset); if (ret == 0) { ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS); From 9e8f1835ea3ab3be83634f34c1bb8b69cd871766 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 8 Feb 2013 14:06:11 +0100 Subject: [PATCH 18/22] block: implement BDRV_O_UNMAP It is better to present homogeneous hardware independent of the storage technology that is chosen on the host, hence we make discard a host parameter; the user can choose whether to pass it down to the image format and protocol, or to ignore it. Using DISCARD with filesystems can cause very severe fragmentation, so it is left default-off for now. This can change later when we implement the "anchor" operation for efficient management of preallocated files. There is still one choice to make: whether DISCARD has an effect on the dirty bitmap or not. I chose yes, though there is a disadvantage: if the guest is buggy and issues discards for data that is in use, there will be no way to migrate storage for that guest without downgrading the machine type to an older one. Signed-off-by: Paolo Bonzini Signed-off-by: Kevin Wolf --- block.c | 25 +++++++++++++++++++++++++ include/block/block.h | 2 ++ qemu-io.c | 11 +++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/block.c b/block.c index f184b376da..4582961965 100644 --- a/block.c +++ b/block.c @@ -580,6 +580,26 @@ static int refresh_total_sectors(BlockDriverState *bs, int64_t hint) return 0; } +/** + * Set open flags for a given discard mode + * + * Return 0 on success, -1 if the discard mode was invalid. + */ +int bdrv_parse_discard_flags(const char *mode, int *flags) +{ + *flags &= ~BDRV_O_UNMAP; + + if (!strcmp(mode, "off") || !strcmp(mode, "ignore")) { + /* do nothing */ + } else if (!strcmp(mode, "on") || !strcmp(mode, "unmap")) { + *flags |= BDRV_O_UNMAP; + } else { + return -1; + } + + return 0; +} + /** * Set open flags for a given cache mode * @@ -4191,6 +4211,11 @@ int coroutine_fn bdrv_co_discard(BlockDriverState *bs, int64_t sector_num, bdrv_reset_dirty(bs, sector_num, nb_sectors); } + /* Do nothing if disabled. */ + if (!(bs->open_flags & BDRV_O_UNMAP)) { + return 0; + } + if (bs->drv->bdrv_co_discard) { return bs->drv->bdrv_co_discard(bs, sector_num, nb_sectors); } else if (bs->drv->bdrv_aio_discard) { diff --git a/include/block/block.h b/include/block/block.h index b4a24da3e9..0f750d7da3 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -84,6 +84,7 @@ typedef struct BlockDevOps { #define BDRV_O_INCOMING 0x0800 /* consistency hint for incoming migration */ #define BDRV_O_CHECK 0x1000 /* open solely for consistency check */ #define BDRV_O_ALLOW_RDWR 0x2000 /* allow reopen to change from r/o to r/w */ +#define BDRV_O_UNMAP 0x4000 /* execute guest UNMAP/TRIM operations */ #define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH) @@ -133,6 +134,7 @@ void bdrv_swap(BlockDriverState *bs_new, BlockDriverState *bs_old); void bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top); void bdrv_delete(BlockDriverState *bs); int bdrv_parse_cache_flags(const char *mode, int *flags); +int bdrv_parse_discard_flags(const char *mode, int *flags); int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags); int bdrv_open_backing_file(BlockDriverState *bs); int bdrv_open(BlockDriverState *bs, const char *filename, int flags, diff --git a/qemu-io.c b/qemu-io.c index 61880932b3..7b3de42773 100644 --- a/qemu-io.c +++ b/qemu-io.c @@ -1899,7 +1899,7 @@ int main(int argc, char **argv) { int readonly = 0; int growable = 0; - const char *sopt = "hVc:rsnmgkt:T:"; + const char *sopt = "hVc:d:rsnmgkt:T:"; const struct option lopt[] = { { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'V' }, @@ -1911,13 +1911,14 @@ int main(int argc, char **argv) { "misalign", 0, NULL, 'm' }, { "growable", 0, NULL, 'g' }, { "native-aio", 0, NULL, 'k' }, + { "discard", 1, NULL, 'd' }, { "cache", 1, NULL, 't' }, { "trace", 1, NULL, 'T' }, { NULL, 0, NULL, 0 } }; int c; int opt_index = 0; - int flags = 0; + int flags = BDRV_O_UNMAP; progname = basename(argv[0]); @@ -1929,6 +1930,12 @@ int main(int argc, char **argv) case 'n': flags |= BDRV_O_NOCACHE | BDRV_O_CACHE_WB; break; + case 'd': + if (bdrv_parse_discard_flags(optarg, &flags) < 0) { + error_report("Invalid discard option: %s", optarg); + exit(1); + } + break; case 'c': add_user_command(optarg); break; From a9384aff5315e7568b6ebc171f4a482e01f06526 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 8 Feb 2013 14:06:12 +0100 Subject: [PATCH 19/22] blockdev: add discard suboption to -drive Add support for BDRV_O_UNMAP from the QEMU command-line. Signed-off-by: Paolo Bonzini Signed-off-by: Kevin Wolf --- blockdev.c | 11 +++++++++++ qemu-options.hx | 2 ++ 2 files changed, 13 insertions(+) diff --git a/blockdev.c b/blockdev.c index b307ed9ba9..0e67d06330 100644 --- a/blockdev.c +++ b/blockdev.c @@ -391,6 +391,13 @@ DriveInfo *drive_init(QemuOpts *opts, BlockInterfaceType block_default_type) } } + if ((buf = qemu_opt_get(opts, "discard")) != NULL) { + if (bdrv_parse_discard_flags(buf, &bdrv_flags) != 0) { + error_report("invalid discard option"); + return NULL; + } + } + bdrv_flags |= BDRV_O_CACHE_WB; if ((buf = qemu_opt_get(opts, "cache")) != NULL) { if (bdrv_parse_cache_flags(buf, &bdrv_flags) != 0) { @@ -1500,6 +1507,10 @@ QemuOptsList qemu_drive_opts = { .name = "file", .type = QEMU_OPT_STRING, .help = "disk image", + },{ + .name = "discard", + .type = QEMU_OPT_STRING, + .help = "discard operation (ignore/off, unmap/on)", },{ .name = "cache", .type = QEMU_OPT_STRING, diff --git a/qemu-options.hx b/qemu-options.hx index 2832d82148..51ff726490 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -440,6 +440,8 @@ These options have the same definition as they have in @option{-hdachs}. @var{cache} is "none", "writeback", "unsafe", "directsync" or "writethrough" and controls how the host cache is used to access block data. @item aio=@var{aio} @var{aio} is "threads", or "native" and selects between pthread based disk I/O and native Linux AIO. +@item discard=@var{discard} +@var{discard} is one of "ignore" (or "off") or "unmap" (or "on") and controls whether @dfn{discard} (also known as @dfn{trim} or @dfn{unmap}) requests are ignored or passed to the filesystem. Some machine types may not support discard requests. @item format=@var{format} Specify which disk @var{format} will be used rather than detecting the format. Can be used to specifiy format=raw to avoid interpreting From ded9d2d5e247dc4d141c01bc8dc99d6ec832f9e8 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 8 Feb 2013 14:06:13 +0100 Subject: [PATCH 20/22] qemu-nbd: add --discard option Similar to --cache and --aio, this option mimics the discard suboption of "-drive". Signed-off-by: Paolo Bonzini Signed-off-by: Kevin Wolf --- qemu-nbd.c | 18 +++++++++++++++--- qemu-nbd.texi | 4 ++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/qemu-nbd.c b/qemu-nbd.c index 0a6091b6a8..e7268d0a9f 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -33,9 +33,10 @@ #include #include -#define SOCKET_PATH "/var/lock/qemu-nbd-%s" -#define QEMU_NBD_OPT_CACHE 1 -#define QEMU_NBD_OPT_AIO 2 +#define SOCKET_PATH "/var/lock/qemu-nbd-%s" +#define QEMU_NBD_OPT_CACHE 1 +#define QEMU_NBD_OPT_AIO 2 +#define QEMU_NBD_OPT_DISCARD 3 static NBDExport *exp; static int verbose; @@ -330,6 +331,7 @@ int main(int argc, char **argv) #ifdef CONFIG_LINUX_AIO { "aio", 1, NULL, QEMU_NBD_OPT_AIO }, #endif + { "discard", 1, NULL, QEMU_NBD_OPT_DISCARD }, { "shared", 1, NULL, 'e' }, { "persistent", 0, NULL, 't' }, { "verbose", 0, NULL, 'v' }, @@ -344,6 +346,7 @@ int main(int argc, char **argv) int ret; int fd; bool seen_cache = false; + bool seen_discard = false; #ifdef CONFIG_LINUX_AIO bool seen_aio = false; #endif @@ -389,6 +392,15 @@ int main(int argc, char **argv) } break; #endif + case QEMU_NBD_OPT_DISCARD: + if (seen_discard) { + errx(EXIT_FAILURE, "--discard can only be specified once"); + } + seen_discard = true; + if (bdrv_parse_discard_flags(optarg, &flags) == -1) { + errx(EXIT_FAILURE, "Invalid discard mode `%s'", optarg); + } + break; case 'b': bindto = optarg; break; diff --git a/qemu-nbd.texi b/qemu-nbd.texi index 3e57200e76..5f3f3e3276 100644 --- a/qemu-nbd.texi +++ b/qemu-nbd.texi @@ -35,6 +35,10 @@ Export QEMU disk image using NBD protocol. @item --aio=@var{aio} choose asynchronous I/O mode between @samp{threads} (the default) and @samp{native} (Linux only). +@item --discard=@var{discard} + toggles whether @dfn{discard} (also known as @dfn{trim} or @dfn{unmap}) + requests are ignored or passed to the filesystem. The default is no + (@samp{--discard=ignore}). @item -c, --connect=@var{dev} connect @var{filename} to NBD device @var{dev} @item -d, --disconnect From 215e47b9ea2cd7926333b7dc683024aa00e0c386 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 8 Feb 2013 14:06:14 +0100 Subject: [PATCH 21/22] blockdev: enable discard by default Because discard is now a host parameter, we can always fake it as enabled in the guest. This is an extension of the current choice to ignore "not supported" errors from the host when discard_granularity is set to nonzero. The default granularity is set to the logical block size or 4k, whichever is largest, because cluster sizes below 4k are rarely used and 4K is a typical block size for files. Signed-off-by: Paolo Bonzini Signed-off-by: Kevin Wolf --- hw/block-common.h | 2 +- hw/ide/qdev.c | 5 ++++- hw/scsi-disk.c | 13 ++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/hw/block-common.h b/hw/block-common.h index bb808f7f56..dd115320c9 100644 --- a/hw/block-common.h +++ b/hw/block-common.h @@ -50,7 +50,7 @@ static inline unsigned int get_physical_block_exp(BlockConf *conf) DEFINE_PROP_UINT32("opt_io_size", _state, _conf.opt_io_size, 0), \ DEFINE_PROP_INT32("bootindex", _state, _conf.bootindex, -1), \ DEFINE_PROP_UINT32("discard_granularity", _state, \ - _conf.discard_granularity, 0) + _conf.discard_granularity, -1) #define DEFINE_BLOCK_CHS_PROPERTIES(_state, _conf) \ DEFINE_PROP_UINT32("cyls", _state, _conf.cyls, 0), \ diff --git a/hw/ide/qdev.c b/hw/ide/qdev.c index c436b38bcb..fd06da7003 100644 --- a/hw/ide/qdev.c +++ b/hw/ide/qdev.c @@ -143,7 +143,10 @@ static int ide_dev_initfn(IDEDevice *dev, IDEDriveKind kind) IDEBus *bus = DO_UPCAST(IDEBus, qbus, dev->qdev.parent_bus); IDEState *s = bus->ifs + dev->unit; - if (dev->conf.discard_granularity && dev->conf.discard_granularity != 512) { + if (dev->conf.discard_granularity == -1) { + dev->conf.discard_granularity = 512; + } else if (dev->conf.discard_granularity && + dev->conf.discard_granularity != 512) { error_report("discard_granularity must be 512 for ide"); return -1; } diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 28e75bbf5b..d41158693e 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -41,9 +41,11 @@ do { printf("scsi-disk: " fmt , ## __VA_ARGS__); } while (0) #include #endif -#define SCSI_DMA_BUF_SIZE 131072 -#define SCSI_MAX_INQUIRY_LEN 256 -#define SCSI_MAX_MODE_LEN 256 +#define SCSI_DMA_BUF_SIZE 131072 +#define SCSI_MAX_INQUIRY_LEN 256 +#define SCSI_MAX_MODE_LEN 256 + +#define DEFAULT_DISCARD_GRANULARITY 4096 typedef struct SCSIDiskState SCSIDiskState; @@ -2059,6 +2061,11 @@ static int scsi_initfn(SCSIDevice *dev) return -1; } + if (s->qdev.conf.discard_granularity == -1) { + s->qdev.conf.discard_granularity = + MAX(s->qdev.conf.logical_block_size, DEFAULT_DISCARD_GRANULARITY); + } + if (!s->version) { s->version = g_strdup(qemu_get_version()); } From bf3caa3dc17552b323cec6831301a22cfc98ecd5 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 8 Feb 2013 14:06:15 +0100 Subject: [PATCH 22/22] pc: add compatibility machine types for 1.4 Adds both pc-i440fx-1.4 and pc-q35-1.4. Signed-off-by: Paolo Bonzini Signed-off-by: Kevin Wolf --- hw/pc.h | 31 +++++++++++++++++++++++++++++++ hw/pc_piix.c | 18 ++++++++++++++++-- hw/pc_q35.c | 19 ++++++++++++++++--- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/hw/pc.h b/hw/pc.h index fbcf43d717..da1b102ef1 100644 --- a/hw/pc.h +++ b/hw/pc.h @@ -187,4 +187,35 @@ void pc_system_firmware_init(MemoryRegion *rom_memory); int e820_add_entry(uint64_t, uint64_t, uint32_t); +#define PC_COMPAT_1_4 \ + {\ + .driver = "scsi-hd",\ + .property = "discard_granularity",\ + .value = stringify(0),\ + },{\ + .driver = "scsi-cd",\ + .property = "discard_granularity",\ + .value = stringify(0),\ + },{\ + .driver = "scsi-disk",\ + .property = "discard_granularity",\ + .value = stringify(0),\ + },{\ + .driver = "ide-hd",\ + .property = "discard_granularity",\ + .value = stringify(0),\ + },{\ + .driver = "ide-cd",\ + .property = "discard_granularity",\ + .value = stringify(0),\ + },{\ + .driver = "ide-drive",\ + .property = "discard_granularity",\ + .value = stringify(0),\ + },{\ + .driver = "virtio-blk-pci",\ + .property = "discard_granularity",\ + .value = stringify(0),\ + } + #endif diff --git a/hw/pc_piix.c b/hw/pc_piix.c index 0af436cfaf..aa9cc81a2d 100644 --- a/hw/pc_piix.c +++ b/hw/pc_piix.c @@ -294,8 +294,8 @@ static void pc_xen_hvm_init(QEMUMachineInitArgs *args) } #endif -static QEMUMachine pc_i440fx_machine_v1_4 = { - .name = "pc-i440fx-1.4", +static QEMUMachine pc_i440fx_machine_v1_5 = { + .name = "pc-i440fx-1.5", .alias = "pc", .desc = "Standard PC (i440FX + PIIX, 1996)", .init = pc_init_pci, @@ -304,7 +304,20 @@ static QEMUMachine pc_i440fx_machine_v1_4 = { DEFAULT_MACHINE_OPTIONS, }; +static QEMUMachine pc_i440fx_machine_v1_4 = { + .name = "pc-i440fx-1.4", + .desc = "Standard PC (i440FX + PIIX, 1996)", + .init = pc_init_pci, + .max_cpus = 255, + .compat_props = (GlobalProperty[]) { + PC_COMPAT_1_4, + { /* end of list */ } + }, + DEFAULT_MACHINE_OPTIONS, +}; + #define PC_COMPAT_1_3 \ + PC_COMPAT_1_4, \ {\ .driver = "usb-tablet",\ .property = "usb_version",\ @@ -679,6 +692,7 @@ static QEMUMachine xenfv_machine = { static void pc_machine_init(void) { + qemu_register_machine(&pc_i440fx_machine_v1_5); qemu_register_machine(&pc_i440fx_machine_v1_4); qemu_register_machine(&pc_machine_v1_3); qemu_register_machine(&pc_machine_v1_2); diff --git a/hw/pc_q35.c b/hw/pc_q35.c index 6f5ff8dcae..e22fb9891d 100644 --- a/hw/pc_q35.c +++ b/hw/pc_q35.c @@ -209,8 +209,8 @@ static void pc_q35_init(QEMUMachineInitArgs *args) } } -static QEMUMachine pc_q35_machine = { - .name = "pc-q35-1.4", +static QEMUMachine pc_q35_machine_v1_5 = { + .name = "pc-q35-1.5", .alias = "q35", .desc = "Standard PC (Q35 + ICH9, 2009)", .init = pc_q35_init, @@ -218,9 +218,22 @@ static QEMUMachine pc_q35_machine = { DEFAULT_MACHINE_OPTIONS, }; +static QEMUMachine pc_q35_machine_v1_4 = { + .name = "pc-q35-1.4", + .desc = "Standard PC (Q35 + ICH9, 2009)", + .init = pc_q35_init, + .max_cpus = 255, + .compat_props = (GlobalProperty[]) { + PC_COMPAT_1_4, + { /* end of list */ } + }, + DEFAULT_MACHINE_OPTIONS, +}; + static void pc_q35_machine_init(void) { - qemu_register_machine(&pc_q35_machine); + qemu_register_machine(&pc_q35_machine_v1_5); + qemu_register_machine(&pc_q35_machine_v1_4); } machine_init(pc_q35_machine_init);