#include "qemu/log.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
+#include "qemu/sockets.h"
#include "qemu/units.h"
#include "qom/object_interfaces.h"
#include "sysemu/block-backend.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_DISABLE = 273,
OPTION_MERGE = 274,
OPTION_BITMAPS = 275,
+ OPTION_FORCE = 276,
};
typedef enum OutputFormat {
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;
}
}
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 'r':
+ rate_limit = cvtnum("rate limit", optarg);
+ if (rate_limit < 0) {
+ return 1;
+ }
+ break;
case OPTION_OBJECT: {
QemuOpts *opts;
opts = qemu_opts_parse_noisily(&qemu_object_opts,
/* 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);
* '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;
}
}
};
#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) {
#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;
bool force_share = false;
bool explict_min_sparse = false;
bool bitmaps = false;
+ int64_t rate_limit = 0;
ImgConvertState s = (ImgConvertState) {
/* Need at least 4k of zeros for sparse detection */
{"bitmaps", no_argument, 0, OPTION_BITMAPS},
{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:Cco:l:S:pt:T:qnm:WUr:",
long_options, NULL);
if (c == -1) {
break;
case 'U':
force_share = true;
break;
+ case 'r':
+ rate_limit = cvtnum("rate limit", optarg);
+ if (rate_limit < 0) {
+ goto fail_getopt;
+ }
+ break;
case OPTION_OBJECT: {
QemuOpts *object_opts;
object_opts = qemu_opts_parse_noisily(&qemu_object_opts,
}
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) {
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);
+ 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, NULL);
if (ret < 0) {
goto out;
goto out;
}
+ if (out_baseimg_param) {
+ if (!qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT)) {
+ warn_report("Deprecated use of backing file without explicit "
+ "backing format");
+ }
+ }
+
/* Check if compression is supported */
if (s.compressed) {
bool encryption =
* 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);
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;
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;
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;
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;
}
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) {
/* 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",
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
+ case OPTION_FORCE:
+ force = true;
+ break;
}
}
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) {
filename = argv[optind];
bitmap = argv[optind + 1];
- blk = img_open(image_opts, filename, fmt, BDRV_O_RDWR, false, false,
- false);
+ /*
+ * 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, 0, false, false, false);
+ src = img_open(false, src_filename, src_fmt, BDRV_O_NO_BACKING,
+ false, false, false);
if (!src) {
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;
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]);
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 */