#include "qemu-common.h"
#include "qemu-version.h"
#include "qapi/error.h"
+#include "qapi/qapi-commands-block-core.h"
#include "qapi/qapi-visit-block-core.h"
#include "qapi/qobject-output-visitor.h"
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qdict.h"
-#include "qapi/qmp/qstring.h"
#include "qemu/cutils.h"
#include "qemu/config-file.h"
#include "qemu/option.h"
#include "qemu/log.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
+#include "qemu/sockets.h"
#include "qemu/units.h"
+#include "qemu/memalign.h"
#include "qom/object_interfaces.h"
#include "sysemu/block-backend.h"
#include "block/block_int.h"
#include "block/qapi.h"
#include "crypto/init.h"
#include "trace/control.h"
+#include "qemu/throttle.h"
+#include "block/throttle-groups.h"
#define QEMU_IMG_VERSION "qemu-img version " QEMU_FULL_VERSION \
"\n" QEMU_COPYRIGHT "\n"
OPTION_SHRINK = 266,
OPTION_SALVAGE = 267,
OPTION_TARGET_IS_ZERO = 268,
+ OPTION_ADD = 269,
+ OPTION_REMOVE = 270,
+ OPTION_CLEAR = 271,
+ OPTION_ENABLE = 272,
+ OPTION_DISABLE = 273,
+ OPTION_MERGE = 274,
+ OPTION_BITMAPS = 275,
+ OPTION_FORCE = 276,
+ OPTION_SKIP_BROKEN = 277,
};
typedef enum OutputFormat {
printf(" %s", name);
}
-static void QEMU_NORETURN GCC_FMT_ATTR(1, 2) error_exit(const char *fmt, ...)
+static void QEMU_NORETURN G_GNUC_PRINTF(1, 2) error_exit(const char *fmt, ...)
{
va_list ap;
error_exit("unrecognized option '%s'", option);
}
-/* Please keep in synch with qemu-img.texi */
+/* Please keep in synch with docs/tools/qemu-img.rst */
static void QEMU_NORETURN help(void)
{
const char *help_msg =
" '-n' skips the target volume creation (useful if the volume is created\n"
" prior to running qemu-img)\n"
"\n"
+ "Parameters to bitmap subcommand:\n"
+ " 'bitmap' is the name of the bitmap to manipulate, through one or more\n"
+ " actions from '--add', '--remove', '--clear', '--enable', '--disable',\n"
+ " or '--merge source'\n"
+ " '-g granularity' sets the granularity for '--add' actions\n"
+ " '-b source' and '-F src_fmt' tell '--merge' actions to find the source\n"
+ " bitmaps from an alternative file\n"
+ "\n"
"Parameters to check subcommand:\n"
" '-r' tries to repair any inconsistencies that are found during the check.\n"
" '-r leaks' repairs only cluster leaks, whereas '-r all' fixes all\n"
" hiding corruption that has already occurred.\n"
"\n"
"Parameters to convert subcommand:\n"
+ " '--bitmaps' copies all top-level persistent bitmaps to destination\n"
" '-m' specifies how many coroutines work in parallel during the convert\n"
" process (defaults to 8)\n"
" '-W' allow to write to the target out of order rather than sequential\n"
exit(EXIT_SUCCESS);
}
-static QemuOptsList qemu_object_opts = {
- .name = "object",
- .implied_opt_name = "qom-type",
- .head = QTAILQ_HEAD_INITIALIZER(qemu_object_opts.head),
- .desc = {
- { }
- },
-};
-
-static bool qemu_img_object_print_help(const char *type, QemuOpts *opts)
-{
- if (user_creatable_print_help(type, opts)) {
- exit(0);
- }
- return true;
-}
-
/*
* Is @optarg safe for accumulate_options()?
* It is when multiple of them can be joined together separated by ','.
},
};
-static int GCC_FMT_ATTR(2, 3) qprintf(bool quiet, const char *fmt, ...)
+static int G_GNUC_PRINTF(2, 3) qprintf(bool quiet, const char *fmt, ...)
{
int ret = 0;
if (!quiet) {
const char *base_filename,
const char *base_fmt)
{
- Error *err = NULL;
-
if (base_filename) {
- qemu_opt_set(opts, BLOCK_OPT_BACKING_FILE, base_filename, &err);
- if (err) {
+ if (!qemu_opt_set(opts, BLOCK_OPT_BACKING_FILE, base_filename,
+ NULL)) {
error_report("Backing file not supported for file format '%s'",
fmt);
- error_free(err);
return -1;
}
}
if (base_fmt) {
- qemu_opt_set(opts, BLOCK_OPT_BACKING_FMT, base_fmt, &err);
- if (err) {
+ if (!qemu_opt_set(opts, BLOCK_OPT_BACKING_FMT, base_fmt, NULL)) {
error_report("Backing file format not supported for file "
"format '%s'", fmt);
- error_free(err);
return -1;
}
}
case 'u':
flags |= BDRV_O_NO_BACKING;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
- goto fail;
- }
- } break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
}
}
}
optind++;
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- goto fail;
- }
-
/* Get image size, if specified */
if (optind < argc) {
int64_t sval;
static void dump_json_image_check(ImageCheck *check, bool quiet)
{
- QString *str;
+ GString *str;
QObject *obj;
Visitor *v = qobject_output_visitor_new(&obj);
visit_type_ImageCheck(v, NULL, &check, &error_abort);
visit_complete(v, &obj);
- str = qobject_to_json_pretty(obj);
+ str = qobject_to_json_pretty(obj, true);
assert(str != NULL);
- qprintf(quiet, "%s\n", qstring_get_str(str));
+ qprintf(quiet, "%s\n", str->str);
qobject_unref(obj);
visit_free(v);
- qobject_unref(str);
+ g_string_free(str, true);
}
static void dump_human_image_check(ImageCheck *check, bool quiet)
case 'U':
force_share = true;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
- return 1;
- }
- } break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
return 1;
}
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- return 1;
- }
-
ret = bdrv_parse_cache_mode(cache, &flags, &writethrough);
if (ret < 0) {
error_report("Invalid source cache option: %s", cache);
static void run_block_job(BlockJob *job, Error **errp)
{
- AioContext *aio_context = blk_get_aio_context(job->blk);
+ uint64_t progress_current, progress_total;
+ AioContext *aio_context = block_job_get_aio_context(job);
int ret = 0;
aio_context_acquire(aio_context);
do {
float progress = 0.0f;
aio_poll(aio_context, true);
- if (job->job.progress.total) {
- progress = (float)job->job.progress.current /
- job->job.progress.total * 100.f;
+
+ progress_get_snapshot(&job->job.progress, &progress_current,
+ &progress_total);
+ if (progress_total) {
+ progress = (float)progress_current / progress_total * 100.f;
}
qemu_progress_print(progress, 0);
} while (!job_is_ready(&job->job) && !job_is_completed(&job->job));
CommonBlockJobCBInfo cbi;
bool image_opts = false;
AioContext *aio_context;
+ int64_t rate_limit = 0;
fmt = NULL;
cache = BDRV_DEFAULT_CACHE;
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
{0, 0, 0, 0}
};
- c = getopt_long(argc, argv, ":f:ht:b:dpq",
+ c = getopt_long(argc, argv, ":f:ht:b:dpqr:",
long_options, NULL);
if (c == -1) {
break;
case 'q':
quiet = true;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
+ case 'r':
+ rate_limit = cvtnum("rate limit", optarg);
+ if (rate_limit < 0) {
return 1;
}
- } break;
+ break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
}
filename = argv[optind++];
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- return 1;
- }
-
flags = BDRV_O_RDWR | BDRV_O_UNMAP;
ret = bdrv_parse_cache_mode(cache, &flags, &writethrough);
if (ret < 0) {
/* This is different from QMP, which by default uses the deepest file in
* the backing chain (i.e., the very base); however, the traditional
* behavior of qemu-img commit is using the immediate backing file. */
- base_bs = backing_bs(bs);
+ base_bs = bdrv_backing_chain_next(bs);
if (!base_bs) {
error_setg(&local_err, "Image does not have a backing file");
goto done;
aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);
- commit_active_start("commit", bs, base_bs, JOB_DEFAULT, 0,
+ commit_active_start("commit", bs, base_bs, JOB_DEFAULT, rate_limit,
BLOCKDEV_ON_ERROR_REPORT, NULL, common_block_job_cb,
&cbi, false, &local_err);
aio_context_release(aio_context);
goto unref_backing;
}
- if (!drop && bs->drv->bdrv_make_empty) {
- ret = bs->drv->bdrv_make_empty(bs);
- if (ret) {
- error_setg_errno(&local_err, -ret, "Could not empty %s",
- filename);
+ if (!drop) {
+ BlockBackend *old_backing_blk;
+
+ old_backing_blk = blk_new_with_bs(bs, BLK_PERM_WRITE, BLK_PERM_ALL,
+ &local_err);
+ if (!old_backing_blk) {
+ goto unref_backing;
+ }
+ ret = blk_make_empty(old_backing_blk, &local_err);
+ blk_unref(old_backing_blk);
+ if (ret == -ENOTSUP) {
+ error_free(local_err);
+ local_err = NULL;
+ } else if (ret < 0) {
goto unref_backing;
}
}
* 'pnum' is set to the number of sectors (including and immediately following
* the first one) that are known to be in the same allocated/unallocated state.
* The function will try to align the end offset to alignment boundaries so
- * that the request will at least end aligned and consequtive requests will
+ * that the request will at least end aligned and consecutive requests will
* also start at an aligned offset.
*/
static int is_allocated_sectors(const uint8_t *buf, int n, int *pnum,
*pnum = 0;
return 0;
}
- is_zero = buffer_is_zero(buf, 512);
+ is_zero = buffer_is_zero(buf, BDRV_SECTOR_SIZE);
for(i = 1; i < n; i++) {
- buf += 512;
- if (is_zero != buffer_is_zero(buf, 512)) {
+ buf += BDRV_SECTOR_SIZE;
+ if (is_zero != buffer_is_zero(buf, BDRV_SECTOR_SIZE)) {
break;
}
}
+ if (i == n) {
+ /*
+ * The whole buf is the same.
+ * No reason to split it into chunks, so return now.
+ */
+ *pnum = i;
+ return !is_zero;
+ }
+
tail = (sector_num + i) & (alignment - 1);
if (tail) {
if (is_zero && i <= tail) {
- /* treat unallocated areas which only consist
- * of a small tail as allocated. */
+ /*
+ * For sure next sector after i is data, and it will rewrite this
+ * tail anyway due to RMW. So, let's just write data now.
+ */
is_zero = false;
}
if (!is_zero) {
- /* align up end offset of allocated areas. */
+ /* If possible, align up end offset of allocated areas. */
i += alignment - tail;
i = MIN(i, n);
} else {
- /* align down end offset of zero areas. */
+ /*
+ * For sure next sector after i is data, and it will rewrite this
+ * tail anyway due to RMW. Better is avoid RMW and write zeroes up
+ * to aligned bound.
+ */
i -= tail;
}
}
/*
* Compares two images. Exit codes:
*
- * 0 - Images are identical
+ * 0 - Images are identical or the requested help was printed
* 1 - Images differ
* >1 - Error occurred
*/
case 'U':
force_share = true;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
- ret = 2;
- goto out4;
+ case OPTION_OBJECT:
+ {
+ Error *local_err = NULL;
+
+ if (!user_creatable_add_from_str(optarg, &local_err)) {
+ if (local_err) {
+ error_report_err(local_err);
+ exit(2);
+ } else {
+ /* Help was printed */
+ exit(EXIT_SUCCESS);
+ }
+ }
+ break;
}
- } break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
filename1 = argv[optind++];
filename2 = argv[optind++];
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- ret = 2;
- goto out4;
- }
-
/* Initialize before goto out */
qemu_progress_init(progress, 2.0);
blk_unref(blk1);
out3:
qemu_progress_end();
-out4:
return ret;
}
+/* Convenience wrapper around qmp_block_dirty_bitmap_merge */
+static void do_dirty_bitmap_merge(const char *dst_node, const char *dst_name,
+ const char *src_node, const char *src_name,
+ Error **errp)
+{
+ BlockDirtyBitmapMergeSource *merge_src;
+ BlockDirtyBitmapMergeSourceList *list = NULL;
+
+ merge_src = g_new0(BlockDirtyBitmapMergeSource, 1);
+ merge_src->type = QTYPE_QDICT;
+ merge_src->u.external.node = g_strdup(src_node);
+ merge_src->u.external.name = g_strdup(src_name);
+ QAPI_LIST_PREPEND(list, merge_src);
+ qmp_block_dirty_bitmap_merge(dst_node, dst_name, list, errp);
+ qapi_free_BlockDirtyBitmapMergeSourceList(list);
+}
+
enum ImgConvertBlockStatus {
BLK_DATA,
BLK_ZERO,
};
#define MAX_COROUTINES 16
+#define CONVERT_THROTTLE_GROUP "img_convert"
typedef struct ImgConvertState {
BlockBackend **src;
int64_t *src_sectors;
+ int *src_alignment;
int src_num;
int64_t total_sectors;
int64_t allocated_sectors;
BlockBackend *target;
bool has_zero_init;
bool compressed;
- bool unallocated_blocks_are_zero;
bool target_is_new;
bool target_has_backing;
int64_t target_backing_sectors; /* negative if unknown */
if (s->target_backing_sectors >= 0) {
if (sector_num >= s->target_backing_sectors) {
- post_backing_zero = s->unallocated_blocks_are_zero;
+ post_backing_zero = true;
} else if (sector_num + n > s->target_backing_sectors) {
/* Split requests around target_backing_sectors (because
* starting from there, zeros are handled differently) */
if (s->sector_next_status <= sector_num) {
uint64_t offset = (sector_num - src_cur_offset) * BDRV_SECTOR_SIZE;
int64_t count;
+ int tail;
+ BlockDriverState *src_bs = blk_bs(s->src[src_cur]);
+ BlockDriverState *base;
+
+ if (s->target_has_backing) {
+ base = bdrv_cow_bs(bdrv_skip_filters(src_bs));
+ } else {
+ base = NULL;
+ }
do {
count = n * BDRV_SECTOR_SIZE;
- if (s->target_has_backing) {
- ret = bdrv_block_status(blk_bs(s->src[src_cur]), offset,
- count, &count, NULL, NULL);
- } else {
- ret = bdrv_block_status_above(blk_bs(s->src[src_cur]), NULL,
- offset, count, &count, NULL,
- NULL);
- }
+ ret = bdrv_block_status_above(src_bs, base, offset, count, &count,
+ NULL, NULL);
if (ret < 0) {
if (s->salvage) {
n = DIV_ROUND_UP(count, BDRV_SECTOR_SIZE);
+ /*
+ * Avoid that s->sector_next_status becomes unaligned to the source
+ * request alignment and/or cluster size to avoid unnecessary read
+ * cycles.
+ */
+ tail = (sector_num - src_cur_offset + n) % s->src_alignment[src_cur];
+ if (n > tail) {
+ n -= tail;
+ }
+
if (ret & BDRV_BLOCK_ZERO) {
s->status = post_backing_zero ? BLK_BACKING_FILE : BLK_ZERO;
} else if (ret & BDRV_BLOCK_DATA) {
s->has_zero_init = bdrv_has_zero_init(blk_bs(s->target));
}
- if (!s->has_zero_init && !s->target_has_backing &&
- bdrv_can_write_zeroes_with_unmap(blk_bs(s->target)))
- {
- ret = blk_make_zero(s->target, BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK);
- if (ret == 0) {
- s->has_zero_init = true;
- }
- }
-
/* Allocate buffer for copied data. For compressed images, only one cluster
* can be copied at a time. */
if (s->compressed) {
return s->ret;
}
+/* Check that bitmaps can be copied, or output an error */
+static int convert_check_bitmaps(BlockDriverState *src, bool skip_broken)
+{
+ BdrvDirtyBitmap *bm;
+
+ if (!bdrv_supports_persistent_dirty_bitmap(src)) {
+ error_report("Source lacks bitmap support");
+ return -1;
+ }
+ FOR_EACH_DIRTY_BITMAP(src, bm) {
+ if (!bdrv_dirty_bitmap_get_persistence(bm)) {
+ continue;
+ }
+ if (!skip_broken && bdrv_dirty_bitmap_inconsistent(bm)) {
+ error_report("Cannot copy inconsistent bitmap '%s'",
+ bdrv_dirty_bitmap_name(bm));
+ error_printf("Try --skip-broken-bitmaps, or "
+ "use 'qemu-img bitmap --remove' to delete it\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int convert_copy_bitmaps(BlockDriverState *src, BlockDriverState *dst,
+ bool skip_broken)
+{
+ BdrvDirtyBitmap *bm;
+ Error *err = NULL;
+
+ FOR_EACH_DIRTY_BITMAP(src, bm) {
+ const char *name;
+
+ if (!bdrv_dirty_bitmap_get_persistence(bm)) {
+ continue;
+ }
+ name = bdrv_dirty_bitmap_name(bm);
+ if (skip_broken && bdrv_dirty_bitmap_inconsistent(bm)) {
+ warn_report("Skipping inconsistent bitmap '%s'", name);
+ continue;
+ }
+ qmp_block_dirty_bitmap_add(dst->node_name, name,
+ true, bdrv_dirty_bitmap_granularity(bm),
+ true, true,
+ true, !bdrv_dirty_bitmap_enabled(bm),
+ &err);
+ if (err) {
+ error_reportf_err(err, "Failed to create bitmap %s: ", name);
+ return -1;
+ }
+
+ do_dirty_bitmap_merge(dst->node_name, name, src->node_name, name,
+ &err);
+ if (err) {
+ error_reportf_err(err, "Failed to populate bitmap %s: ", name);
+ qmp_block_dirty_bitmap_remove(dst->node_name, name, NULL);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
#define MAX_BUF_SECTORS 32768
+static void set_rate_limit(BlockBackend *blk, int64_t rate_limit)
+{
+ ThrottleConfig cfg;
+
+ throttle_config_init(&cfg);
+ cfg.buckets[THROTTLE_BPS_WRITE].avg = rate_limit;
+
+ blk_io_limits_enable(blk, CONVERT_THROTTLE_GROUP);
+ blk_set_io_limits(blk, &cfg);
+}
+
static int img_convert(int argc, char **argv)
{
- int c, bs_i, flags, src_flags = 0;
+ int c, bs_i, flags, src_flags = BDRV_O_NO_SHARE;
const char *fmt = NULL, *out_fmt = NULL, *cache = "unsafe",
*src_cache = BDRV_DEFAULT_CACHE, *out_baseimg = NULL,
- *out_filename, *out_baseimg_param, *snapshot_name = NULL;
+ *out_filename, *out_baseimg_param, *snapshot_name = NULL,
+ *backing_fmt = NULL;
BlockDriver *drv = NULL, *proto_drv = NULL;
BlockDriverInfo bdi;
BlockDriverState *out_bs;
int64_t ret = -EINVAL;
bool force_share = false;
bool explict_min_sparse = false;
+ bool bitmaps = false;
+ bool skip_broken = false;
+ int64_t rate_limit = 0;
ImgConvertState s = (ImgConvertState) {
/* Need at least 4k of zeros for sparse detection */
{"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS},
{"salvage", no_argument, 0, OPTION_SALVAGE},
{"target-is-zero", no_argument, 0, OPTION_TARGET_IS_ZERO},
+ {"bitmaps", no_argument, 0, OPTION_BITMAPS},
+ {"skip-broken-bitmaps", no_argument, 0, OPTION_SKIP_BROKEN},
{0, 0, 0, 0}
};
- c = getopt_long(argc, argv, ":hf:O:B:Cco:l:S:pt:T:qnm:WU",
+ c = getopt_long(argc, argv, ":hf:O:B:CcF:o:l:S:pt:T:qnm:WUr:",
long_options, NULL);
if (c == -1) {
break;
case 'c':
s.compressed = true;
break;
+ case 'F':
+ backing_fmt = optarg;
+ break;
case 'o':
if (accumulate_options(&options, optarg) < 0) {
goto fail_getopt;
case 'U':
force_share = true;
break;
- case OPTION_OBJECT: {
- QemuOpts *object_opts;
- object_opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!object_opts) {
+ case 'r':
+ rate_limit = cvtnum("rate limit", optarg);
+ if (rate_limit < 0) {
goto fail_getopt;
}
break;
- }
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
*/
s.has_zero_init = true;
break;
+ case OPTION_BITMAPS:
+ bitmaps = true;
+ break;
+ case OPTION_SKIP_BROKEN:
+ skip_broken = true;
+ break;
}
}
out_fmt = "raw";
}
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
+ if (skip_broken && !bitmaps) {
+ error_report("Use of --skip-broken-bitmaps requires --bitmaps");
goto fail_getopt;
}
}
if (skip_create && options) {
- warn_report("-o has no effect when skipping image creation");
- warn_report("This will become an error in future QEMU versions.");
+ error_report("-o has no effect when skipping image creation");
+ goto fail_getopt;
}
if (s.has_zero_init && !skip_create) {
goto fail_getopt;
}
-
/* ret is still -EINVAL until here */
ret = bdrv_parse_cache_mode(src_cache, &src_flags, &src_writethrough);
if (ret < 0) {
s.src = g_new0(BlockBackend *, s.src_num);
s.src_sectors = g_new(int64_t, s.src_num);
+ s.src_alignment = g_new(int, s.src_num);
for (bs_i = 0; bs_i < s.src_num; bs_i++) {
+ BlockDriverState *src_bs;
s.src[bs_i] = img_open(image_opts, argv[optind + bs_i],
fmt, src_flags, src_writethrough, s.quiet,
force_share);
ret = -1;
goto out;
}
+ src_bs = blk_bs(s.src[bs_i]);
+ s.src_alignment[bs_i] = DIV_ROUND_UP(src_bs->bl.request_alignment,
+ BDRV_SECTOR_SIZE);
+ if (!bdrv_get_info(src_bs, &bdi)) {
+ s.src_alignment[bs_i] = MAX(s.src_alignment[bs_i],
+ bdi.cluster_size / BDRV_SECTOR_SIZE);
+ }
s.total_sectors += s.src_sectors[bs_i];
}
opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
if (options) {
- qemu_opts_do_parse(opts, options, NULL, &local_err);
- if (local_err) {
+ if (!qemu_opts_do_parse(opts, options, NULL, &local_err)) {
error_report_err(local_err);
ret = -1;
goto out;
}
}
- qemu_opt_set_number(opts, BLOCK_OPT_SIZE, s.total_sectors * 512,
- &error_abort);
- ret = add_old_style_options(out_fmt, opts, out_baseimg, NULL);
+ qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
+ s.total_sectors * BDRV_SECTOR_SIZE, &error_abort);
+ ret = add_old_style_options(out_fmt, opts, out_baseimg, backing_fmt);
if (ret < 0) {
goto out;
}
goto out;
}
+ if (out_baseimg_param) {
+ if (!qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT)) {
+ error_report("Use of backing file requires explicit "
+ "backing format");
+ ret = -1;
+ goto out;
+ }
+ }
+
/* Check if compression is supported */
if (s.compressed) {
bool encryption =
}
}
+ /* Determine if bitmaps need copying */
+ if (bitmaps) {
+ if (s.src_num > 1) {
+ error_report("Copying bitmaps only possible with single source");
+ ret = -1;
+ goto out;
+ }
+ ret = convert_check_bitmaps(blk_bs(s.src[0]), skip_broken);
+ if (ret < 0) {
+ goto out;
+ }
+ }
+
/*
* The later open call will need any decryption secrets, and
* bdrv_create() will purge "opts", so extract them now before
if (!skip_create) {
open_opts = qdict_new();
qemu_opt_foreach(opts, img_add_key_secrets, open_opts, &error_abort);
- }
- if (!skip_create) {
/* Create the new image */
ret = bdrv_create(drv, out_filename, opts, &local_err);
if (ret < 0) {
goto out;
}
+ if (flags & BDRV_O_NOCACHE) {
+ /*
+ * If we open the target with O_DIRECT, it may be necessary to
+ * extend its size to align to the physical sector size.
+ */
+ flags |= BDRV_O_RESIZE;
+ }
+
if (skip_create) {
s.target = img_open(tgt_image_opts, out_filename, out_fmt,
flags, writethrough, s.quiet, false);
}
out_bs = blk_bs(s.target);
+ if (bitmaps && !bdrv_supports_persistent_dirty_bitmap(out_bs)) {
+ error_report("Format driver '%s' does not support bitmaps",
+ out_bs->drv->format_name);
+ ret = -1;
+ goto out;
+ }
+
if (s.compressed && !block_driver_can_compress(out_bs->drv)) {
error_report("Compression not supported for this file format");
ret = -1;
* s.target_backing_sectors has to be negative, which it will
* be automatically). The backing file length is used only
* for optimizations, so such a case is not fatal. */
- s.target_backing_sectors = bdrv_nb_sectors(out_bs->backing->bs);
+ s.target_backing_sectors =
+ bdrv_nb_sectors(bdrv_backing_chain_next(out_bs));
} else {
s.target_backing_sectors = -1;
}
} else {
s.compressed = s.compressed || bdi.needs_compressed_writes;
s.cluster_sectors = bdi.cluster_size / BDRV_SECTOR_SIZE;
- s.unallocated_blocks_are_zero = bdi.unallocated_blocks_are_zero;
+ }
+
+ if (rate_limit) {
+ set_rate_limit(s.target, rate_limit);
}
ret = convert_do_copy(&s);
+
+ /* Now copy the bitmaps */
+ if (bitmaps && ret == 0) {
+ ret = convert_copy_bitmaps(blk_bs(s.src[0]), out_bs, skip_broken);
+ }
+
out:
if (!ret) {
qemu_progress_print(100, 0);
qemu_progress_end();
qemu_opts_del(opts);
qemu_opts_free(create_opts);
- qemu_opts_del(sn_opts);
qobject_unref(open_opts);
blk_unref(s.target);
if (s.src) {
g_free(s.src);
}
g_free(s.src_sectors);
+ g_free(s.src_alignment);
fail_getopt:
+ qemu_opts_del(sn_opts);
g_free(options);
return !!ret;
static void dump_json_image_info_list(ImageInfoList *list)
{
- QString *str;
+ GString *str;
QObject *obj;
Visitor *v = qobject_output_visitor_new(&obj);
visit_type_ImageInfoList(v, NULL, &list, &error_abort);
visit_complete(v, &obj);
- str = qobject_to_json_pretty(obj);
+ str = qobject_to_json_pretty(obj, true);
assert(str != NULL);
- printf("%s\n", qstring_get_str(str));
+ printf("%s\n", str->str);
qobject_unref(obj);
visit_free(v);
- qobject_unref(str);
+ g_string_free(str, true);
}
static void dump_json_image_info(ImageInfo *info)
{
- QString *str;
+ GString *str;
QObject *obj;
Visitor *v = qobject_output_visitor_new(&obj);
visit_type_ImageInfo(v, NULL, &info, &error_abort);
visit_complete(v, &obj);
- str = qobject_to_json_pretty(obj);
+ str = qobject_to_json_pretty(obj, true);
assert(str != NULL);
- printf("%s\n", qstring_get_str(str));
+ printf("%s\n", str->str);
qobject_unref(obj);
visit_free(v);
- qobject_unref(str);
+ g_string_free(str, true);
}
static void dump_human_image_info_list(ImageInfoList *list)
bool chain, bool force_share)
{
ImageInfoList *head = NULL;
- ImageInfoList **last = &head;
+ ImageInfoList **tail = &head;
GHashTable *filenames;
Error *err = NULL;
BlockBackend *blk;
BlockDriverState *bs;
ImageInfo *info;
- ImageInfoList *elem;
if (g_hash_table_lookup_extended(filenames, filename, NULL, NULL)) {
error_report("Backing file '%s' creates an infinite loop.",
goto err;
}
- elem = g_new0(ImageInfoList, 1);
- elem->value = info;
- *last = elem;
- last = &elem->next;
+ QAPI_LIST_APPEND(tail, info);
blk_unref(blk);
case OPTION_BACKING_CHAIN:
chain = true;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
- return 1;
- }
- } break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
return 1;
}
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- return 1;
- }
-
list = collect_image_info_list(image_opts, filename, fmt, chain,
force_share);
if (!list) {
}
break;
case OFORMAT_JSON:
- printf("%s{ \"start\": %"PRId64", \"length\": %"PRId64","
- " \"depth\": %"PRId64", \"zero\": %s, \"data\": %s",
- (e->start == 0 ? "[" : ",\n"),
- e->start, e->length, e->depth,
+ printf("{ \"start\": %"PRId64", \"length\": %"PRId64","
+ " \"depth\": %"PRId64", \"present\": %s, \"zero\": %s,"
+ " \"data\": %s", e->start, e->length, e->depth,
+ e->present ? "true" : "false",
e->zero ? "true" : "false",
e->data ? "true" : "false");
if (e->has_offset) {
}
putchar('}');
- if (!next) {
- printf("]\n");
+ if (next) {
+ puts(",");
}
break;
}
depth = 0;
for (;;) {
+ bs = bdrv_skip_filters(bs);
ret = bdrv_block_status(bs, offset, bytes, &bytes, &map, &file);
if (ret < 0) {
return ret;
if (ret & (BDRV_BLOCK_ZERO|BDRV_BLOCK_DATA)) {
break;
}
- bs = backing_bs(bs);
+ bs = bdrv_cow_bs(bs);
if (bs == NULL) {
ret = 0;
break;
.offset = map,
.has_offset = has_offset,
.depth = depth,
+ .present = !!(ret & BDRV_BLOCK_ALLOCATED),
.has_filename = filename,
.filename = filename,
};
if (curr->zero != next->zero ||
curr->data != next->data ||
curr->depth != next->depth ||
+ curr->present != next->present ||
curr->has_filename != next->has_filename ||
curr->has_offset != next->has_offset) {
return false;
int ret = 0;
bool image_opts = false;
bool force_share = false;
+ int64_t start_offset = 0;
+ int64_t max_length = -1;
fmt = NULL;
output = NULL;
{"object", required_argument, 0, OPTION_OBJECT},
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
{"force-share", no_argument, 0, 'U'},
+ {"start-offset", required_argument, 0, 's'},
+ {"max-length", required_argument, 0, 'l'},
{0, 0, 0, 0}
};
- c = getopt_long(argc, argv, ":f:hU",
+ c = getopt_long(argc, argv, ":f:s:l:hU",
long_options, &option_index);
if (c == -1) {
break;
case OPTION_OUTPUT:
output = optarg;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
+ case 's':
+ start_offset = cvtnum("start offset", optarg);
+ if (start_offset < 0) {
+ return 1;
+ }
+ break;
+ case 'l':
+ max_length = cvtnum("max length", optarg);
+ if (max_length < 0) {
return 1;
}
- } break;
+ break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
return 1;
}
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- return 1;
- }
-
blk = img_open(image_opts, filename, fmt, 0, false, false, force_share);
if (!blk) {
return 1;
if (output_format == OFORMAT_HUMAN) {
printf("%-16s%-16s%-16s%s\n", "Offset", "Length", "Mapped to", "File");
+ } else if (output_format == OFORMAT_JSON) {
+ putchar('[');
}
length = blk_getlength(blk);
error_report("Failed to get size for '%s'", filename);
return 1;
}
+ if (max_length != -1) {
+ length = MIN(start_offset + max_length, length);
+ }
+ curr.start = start_offset;
while (curr.start + curr.length < length) {
int64_t offset = curr.start + curr.length;
- int64_t n;
+ int64_t n = length - offset;
- /* Probe up to 1 GiB at a time. */
- n = MIN(1 * GiB, length - offset);
ret = get_block_status(bs, offset, n, &next);
-
if (ret < 0) {
error_report("Could not read file metadata: %s", strerror(-ret));
goto out;
}
ret = dump_map_entry(output_format, &curr, NULL);
+ if (output_format == OFORMAT_JSON) {
+ puts("]");
+ }
out:
blk_unref(blk);
case 'U':
force_share = true;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
- return 1;
- }
- } break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
}
filename = argv[optind++];
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- return 1;
- }
-
/* Open the image */
blk = img_open(image_opts, filename, NULL, bdrv_oflags, false, quiet,
force_share);
uint8_t *buf_old = NULL;
uint8_t *buf_new = NULL;
BlockDriverState *bs = NULL, *prefix_chain_bs = NULL;
+ BlockDriverState *unfiltered_bs;
char *filename;
const char *fmt, *cache, *src_cache, *out_basefmt, *out_baseimg;
int c, flags, src_flags, ret;
case 'q':
quiet = true;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
- return 1;
- }
- } break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
}
filename = argv[optind++];
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- return 1;
- }
-
qemu_progress_init(progress, 2.0);
qemu_progress_print(0, 100);
}
bs = blk_bs(blk);
+ unfiltered_bs = bdrv_skip_filters(bs);
+
if (out_basefmt != NULL) {
if (bdrv_find_format(out_basefmt) == NULL) {
error_report("Invalid format name: '%s'", out_basefmt);
/* For safe rebasing we need to compare old and new backing file */
if (!unsafe) {
QDict *options = NULL;
- BlockDriverState *base_bs = backing_bs(bs);
+ BlockDriverState *base_bs = bdrv_cow_bs(unfiltered_bs);
if (base_bs) {
blk_old_backing = blk_new(qemu_get_aio_context(),
n = MIN(IO_BUF_SIZE, size - offset);
/* If the cluster is allocated, we don't need to take action */
- ret = bdrv_is_allocated(bs, offset, n, &n);
+ ret = bdrv_is_allocated(unfiltered_bs, offset, n, &n);
if (ret < 0) {
error_report("error while reading image metadata: %s",
strerror(-ret));
* If cluster wasn't changed since prefix_chain, we don't need
* to take action
*/
- ret = bdrv_is_allocated_above(backing_bs(bs), prefix_chain_bs,
- false, offset, n, &n);
+ ret = bdrv_is_allocated_above(bdrv_cow_bs(unfiltered_bs),
+ prefix_chain_bs, false,
+ offset, n, &n);
if (ret < 0) {
error_report("error while reading image metadata: %s",
strerror(-ret));
* doesn't change when we switch the backing file.
*/
if (out_baseimg && *out_baseimg) {
- ret = bdrv_change_backing_file(bs, out_baseimg, out_basefmt);
+ ret = bdrv_change_backing_file(unfiltered_bs, out_baseimg, out_basefmt,
+ true);
} else {
- ret = bdrv_change_backing_file(bs, NULL, NULL);
+ ret = bdrv_change_backing_file(unfiltered_bs, NULL, NULL, false);
}
if (ret == -ENOSPC) {
error_report("Could not change the backing file to '%s': No "
"space left in the file header", out_baseimg);
+ } else if (ret == -EINVAL && out_baseimg && !out_basefmt) {
+ error_report("Could not change the backing file to '%s': backing "
+ "format must be specified", out_baseimg);
} else if (ret < 0) {
error_report("Could not change the backing file to '%s': %s",
out_baseimg, strerror(-ret));
case 'q':
quiet = true;
break;
- case OPTION_OBJECT: {
- QemuOpts *opts;
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
- return 1;
- }
- } break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
}
filename = argv[optind++];
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- return 1;
- }
-
/* Choose grow, shrink, or absolute resize mode */
switch (size[0]) {
case '+':
/* Parse size */
param = qemu_opts_create(&resize_options, NULL, 0, &error_abort);
- qemu_opt_set(param, BLOCK_OPT_SIZE, size, &err);
- if (err) {
+ if (!qemu_opt_set(param, BLOCK_OPT_SIZE, size, &err)) {
error_report_err(err);
ret = -1;
qemu_opts_del(param);
}
if (total_size < current_size && !shrink) {
+ error_report("Use the --shrink option to perform a shrink operation.");
warn_report("Shrinking an image will delete all data beyond the "
"shrunken image's end. Before performing such an "
"operation, make sure there is no important data there.");
-
- if (g_strcmp0(bdrv_get_format_name(blk_bs(blk)), "raw") != 0) {
- error_report(
- "Use the --shrink option to perform a shrink operation.");
- ret = -1;
- goto out;
- } else {
- warn_report("Using the --shrink option will suppress this message. "
- "Note that future versions of qemu-img may refuse to "
- "shrink images without this option.");
- }
+ ret = -1;
+ goto out;
}
/*
return 1;
}
- /* Every driver supporting amendment must have create_opts */
- assert(drv->create_opts);
+ /* Every driver supporting amendment must have amend_opts */
+ assert(drv->amend_opts);
- printf("Creation options for '%s':\n", format);
- qemu_opts_print_help(drv->create_opts, false);
- printf("\nNote that not all of these options may be amendable.\n");
+ printf("Amend options for '%s':\n", format);
+ qemu_opts_print_help(drv->amend_opts, false);
return 0;
}
Error *err = NULL;
int c, ret = 0;
char *options = NULL;
- QemuOptsList *create_opts = NULL;
+ QemuOptsList *amend_opts = NULL;
QemuOpts *opts = NULL;
const char *fmt = NULL, *filename, *cache;
int flags;
BlockBackend *blk = NULL;
BlockDriverState *bs = NULL;
bool image_opts = false;
+ bool force = false;
cache = BDRV_DEFAULT_CACHE;
for (;;) {
{"help", no_argument, 0, 'h'},
{"object", required_argument, 0, OPTION_OBJECT},
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
+ {"force", no_argument, 0, OPTION_FORCE},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, ":ho:f:t:pq",
quiet = true;
break;
case OPTION_OBJECT:
- opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!opts) {
- ret = -1;
- goto out_no_progress;
- }
+ user_creatable_process_cmdline(optarg);
break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
+ case OPTION_FORCE:
+ force = true;
+ break;
}
}
error_exit("Must specify options (-o)");
}
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- ret = -1;
- goto out_no_progress;
- }
-
if (quiet) {
progress = false;
}
goto out;
}
- /* Every driver supporting amendment must have create_opts */
- assert(bs->drv->create_opts);
+ /* Every driver supporting amendment must have amend_opts */
+ assert(bs->drv->amend_opts);
+
+ amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
+ opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
+ if (!qemu_opts_do_parse(opts, options, NULL, &err)) {
+ /* Try to parse options using the create options */
+ amend_opts = qemu_opts_append(amend_opts, bs->drv->create_opts);
+ qemu_opts_del(opts);
+ opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
+ if (qemu_opts_do_parse(opts, options, NULL, NULL)) {
+ error_append_hint(&err,
+ "This option is only supported for image creation\n");
+ }
- create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
- opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
- qemu_opts_do_parse(opts, options, NULL, &err);
- if (err) {
error_report_err(err);
ret = -1;
goto out;
/* In case the driver does not call amend_status_cb() */
qemu_progress_print(0.f, 0);
- ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, &err);
+ ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, force, &err);
qemu_progress_print(100.f, 0);
if (ret < 0) {
error_report_err(err);
out_no_progress:
blk_unref(blk);
qemu_opts_del(opts);
- qemu_opts_free(create_opts);
+ qemu_opts_free(amend_opts);
g_free(options);
if (ret) {
return 0;
}
+enum ImgBitmapAct {
+ BITMAP_ADD,
+ BITMAP_REMOVE,
+ BITMAP_CLEAR,
+ BITMAP_ENABLE,
+ BITMAP_DISABLE,
+ BITMAP_MERGE,
+};
+typedef struct ImgBitmapAction {
+ enum ImgBitmapAct act;
+ const char *src; /* only used for merge */
+ QSIMPLEQ_ENTRY(ImgBitmapAction) next;
+} ImgBitmapAction;
+
+static int img_bitmap(int argc, char **argv)
+{
+ Error *err = NULL;
+ int c, ret = 1;
+ QemuOpts *opts = NULL;
+ const char *fmt = NULL, *src_fmt = NULL, *src_filename = NULL;
+ const char *filename, *bitmap;
+ BlockBackend *blk = NULL, *src = NULL;
+ BlockDriverState *bs = NULL, *src_bs = NULL;
+ bool image_opts = false;
+ int64_t granularity = 0;
+ bool add = false, merge = false;
+ QSIMPLEQ_HEAD(, ImgBitmapAction) actions;
+ ImgBitmapAction *act, *act_next;
+ const char *op;
+
+ QSIMPLEQ_INIT(&actions);
+
+ for (;;) {
+ static const struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"object", required_argument, 0, OPTION_OBJECT},
+ {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
+ {"add", no_argument, 0, OPTION_ADD},
+ {"remove", no_argument, 0, OPTION_REMOVE},
+ {"clear", no_argument, 0, OPTION_CLEAR},
+ {"enable", no_argument, 0, OPTION_ENABLE},
+ {"disable", no_argument, 0, OPTION_DISABLE},
+ {"merge", required_argument, 0, OPTION_MERGE},
+ {"granularity", required_argument, 0, 'g'},
+ {"source-file", required_argument, 0, 'b'},
+ {"source-format", required_argument, 0, 'F'},
+ {0, 0, 0, 0}
+ };
+ c = getopt_long(argc, argv, ":b:f:F:g:h", long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case ':':
+ missing_argument(argv[optind - 1]);
+ break;
+ case '?':
+ unrecognized_option(argv[optind - 1]);
+ break;
+ case 'h':
+ help();
+ break;
+ case 'b':
+ src_filename = optarg;
+ break;
+ case 'f':
+ fmt = optarg;
+ break;
+ case 'F':
+ src_fmt = optarg;
+ break;
+ case 'g':
+ granularity = cvtnum("granularity", optarg);
+ if (granularity < 0) {
+ return 1;
+ }
+ break;
+ case OPTION_ADD:
+ act = g_new0(ImgBitmapAction, 1);
+ act->act = BITMAP_ADD;
+ QSIMPLEQ_INSERT_TAIL(&actions, act, next);
+ add = true;
+ break;
+ case OPTION_REMOVE:
+ act = g_new0(ImgBitmapAction, 1);
+ act->act = BITMAP_REMOVE;
+ QSIMPLEQ_INSERT_TAIL(&actions, act, next);
+ break;
+ case OPTION_CLEAR:
+ act = g_new0(ImgBitmapAction, 1);
+ act->act = BITMAP_CLEAR;
+ QSIMPLEQ_INSERT_TAIL(&actions, act, next);
+ break;
+ case OPTION_ENABLE:
+ act = g_new0(ImgBitmapAction, 1);
+ act->act = BITMAP_ENABLE;
+ QSIMPLEQ_INSERT_TAIL(&actions, act, next);
+ break;
+ case OPTION_DISABLE:
+ act = g_new0(ImgBitmapAction, 1);
+ act->act = BITMAP_DISABLE;
+ QSIMPLEQ_INSERT_TAIL(&actions, act, next);
+ break;
+ case OPTION_MERGE:
+ act = g_new0(ImgBitmapAction, 1);
+ act->act = BITMAP_MERGE;
+ act->src = optarg;
+ QSIMPLEQ_INSERT_TAIL(&actions, act, next);
+ merge = true;
+ break;
+ case OPTION_OBJECT:
+ user_creatable_process_cmdline(optarg);
+ break;
+ case OPTION_IMAGE_OPTS:
+ image_opts = true;
+ break;
+ }
+ }
+
+ if (QSIMPLEQ_EMPTY(&actions)) {
+ error_report("Need at least one of --add, --remove, --clear, "
+ "--enable, --disable, or --merge");
+ goto out;
+ }
+
+ if (granularity && !add) {
+ error_report("granularity only supported with --add");
+ goto out;
+ }
+ if (src_fmt && !src_filename) {
+ error_report("-F only supported with -b");
+ goto out;
+ }
+ if (src_filename && !merge) {
+ error_report("Merge bitmap source file only supported with "
+ "--merge");
+ goto out;
+ }
+
+ if (optind != argc - 2) {
+ error_report("Expecting filename and bitmap name");
+ goto out;
+ }
+
+ filename = argv[optind];
+ bitmap = argv[optind + 1];
+
+ /*
+ * No need to open backing chains; we will be manipulating bitmaps
+ * directly in this image without reference to image contents.
+ */
+ blk = img_open(image_opts, filename, fmt, BDRV_O_RDWR | BDRV_O_NO_BACKING,
+ false, false, false);
+ if (!blk) {
+ goto out;
+ }
+ bs = blk_bs(blk);
+ if (src_filename) {
+ src = img_open(false, src_filename, src_fmt, BDRV_O_NO_BACKING,
+ false, false, false);
+ if (!src) {
+ goto out;
+ }
+ src_bs = blk_bs(src);
+ } else {
+ src_bs = bs;
+ }
+
+ QSIMPLEQ_FOREACH_SAFE(act, &actions, next, act_next) {
+ switch (act->act) {
+ case BITMAP_ADD:
+ qmp_block_dirty_bitmap_add(bs->node_name, bitmap,
+ !!granularity, granularity, true, true,
+ false, false, &err);
+ op = "add";
+ break;
+ case BITMAP_REMOVE:
+ qmp_block_dirty_bitmap_remove(bs->node_name, bitmap, &err);
+ op = "remove";
+ break;
+ case BITMAP_CLEAR:
+ qmp_block_dirty_bitmap_clear(bs->node_name, bitmap, &err);
+ op = "clear";
+ break;
+ case BITMAP_ENABLE:
+ qmp_block_dirty_bitmap_enable(bs->node_name, bitmap, &err);
+ op = "enable";
+ break;
+ case BITMAP_DISABLE:
+ qmp_block_dirty_bitmap_disable(bs->node_name, bitmap, &err);
+ op = "disable";
+ break;
+ case BITMAP_MERGE:
+ do_dirty_bitmap_merge(bs->node_name, bitmap, src_bs->node_name,
+ act->src, &err);
+ op = "merge";
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ if (err) {
+ error_reportf_err(err, "Operation %s on bitmap %s failed: ",
+ op, bitmap);
+ goto out;
+ }
+ g_free(act);
+ }
+
+ ret = 0;
+
+ out:
+ blk_unref(src);
+ blk_unref(blk);
+ qemu_opts_del(opts);
+ return ret;
+}
+
#define C_BS 01
#define C_COUNT 02
#define C_IF 04
force_share = true;
break;
case OPTION_OBJECT:
- if (!qemu_opts_parse_noisily(&qemu_object_opts, optarg, true)) {
- ret = -1;
- goto out;
- }
+ user_creatable_process_cmdline(optarg);
break;
case OPTION_IMAGE_OPTS:
image_opts = true;
goto out;
}
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- ret = -1;
- goto out;
- }
-
blk1 = img_open(image_opts, in.filename, fmt, 0, false, false,
force_share);
static void dump_json_block_measure_info(BlockMeasureInfo *info)
{
- QString *str;
+ GString *str;
QObject *obj;
Visitor *v = qobject_output_visitor_new(&obj);
visit_type_BlockMeasureInfo(v, NULL, &info, &error_abort);
visit_complete(v, &obj);
- str = qobject_to_json_pretty(obj);
+ str = qobject_to_json_pretty(obj, true);
assert(str != NULL);
- printf("%s\n", qstring_get_str(str));
+ printf("%s\n", str->str);
qobject_unref(obj);
visit_free(v);
- qobject_unref(str);
+ g_string_free(str, true);
}
static int img_measure(int argc, char **argv)
force_share = true;
break;
case OPTION_OBJECT:
- object_opts = qemu_opts_parse_noisily(&qemu_object_opts,
- optarg, true);
- if (!object_opts) {
- goto out;
- }
+ user_creatable_process_cmdline(optarg);
break;
case OPTION_IMAGE_OPTS:
image_opts = true;
}
}
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- qemu_img_object_print_help, &error_fatal)) {
- goto out;
- }
-
if (argc - optind > 1) {
error_report("At most one filename argument is allowed.");
goto out;
create_opts = qemu_opts_append(create_opts, bdrv_file.create_opts);
opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
if (options) {
- qemu_opts_do_parse(opts, options, NULL, &local_err);
- if (local_err) {
+ if (!qemu_opts_do_parse(opts, options, NULL, &local_err)) {
error_report_err(local_err);
error_report("Invalid options for file format '%s'", out_fmt);
goto out;
if (output_format == OFORMAT_HUMAN) {
printf("required size: %" PRIu64 "\n", info->required);
printf("fully allocated size: %" PRIu64 "\n", info->fully_allocated);
+ if (info->has_bitmaps) {
+ printf("bitmaps size: %" PRIu64 "\n", info->bitmaps);
+ }
} else {
dump_json_block_measure_info(info);
}
{
const img_cmd_t *cmd;
const char *cmdname;
- Error *local_error = NULL;
- char *trace_file = NULL;
int c;
static const struct option long_options[] = {
{"help", no_argument, 0, 'h'},
signal(SIGPIPE, SIG_IGN);
#endif
+ socket_init();
error_init(argv[0]);
module_call_init(MODULE_INIT_TRACE);
qemu_init_exec_dir(argv[0]);
- if (qemu_init_main_loop(&local_error)) {
- error_report_err(local_error);
- exit(EXIT_FAILURE);
- }
+ qemu_init_main_loop(&error_fatal);
qcrypto_init(&error_fatal);
error_exit("Not enough arguments");
}
- qemu_add_opts(&qemu_object_opts);
qemu_add_opts(&qemu_source_opts);
qemu_add_opts(&qemu_trace_opts);
printf(QEMU_IMG_VERSION);
return 0;
case 'T':
- g_free(trace_file);
- trace_file = trace_opt_parse(optarg);
+ trace_opt_parse(optarg);
break;
}
}
if (!trace_init_backends()) {
exit(1);
}
- trace_init_file(trace_file);
+ trace_init_file();
qemu_set_log(LOG_TRACE);
/* find the command */